diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs
index 08da2df8a6..fdf66b7005 100644
--- a/web/.eslintrc.cjs
+++ b/web/.eslintrc.cjs
@@ -1,43 +1,39 @@
 /** @type {import('eslint').Linter.Config} */
 module.exports = {
-	root: true,
-	extends: [
-		'eslint:recommended',
-		'plugin:@typescript-eslint/recommended',
-		'plugin:svelte/recommended'
-	],
-	parser: '@typescript-eslint/parser',
-	plugins: ['@typescript-eslint'],
-	parserOptions: {
-		sourceType: 'module',
-		ecmaVersion: 2020,
-		extraFileExtensions: ['.svelte']
-	},
-	env: {
-		browser: true,
-		es2017: true,
-		node: true
-	},
-	overrides: [
-		{
-			files: ['*.svelte'],
-			parser: 'svelte-eslint-parser',
-			parserOptions: {
-				parser: '@typescript-eslint/parser'
-			}
-		}
-	],
-	globals: {
-		NodeJS: true
-	},
-	rules: {
-		'@typescript-eslint/no-unused-vars': [
-			'warn',
-			{
-				// Allow underscore (_) variables
-				argsIgnorePattern: '^_$',
-				varsIgnorePattern: '^_$'
-			}
-		]
-	}
+  root: true,
+  extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:svelte/recommended'],
+  parser: '@typescript-eslint/parser',
+  plugins: ['@typescript-eslint'],
+  parserOptions: {
+    sourceType: 'module',
+    ecmaVersion: 2020,
+    extraFileExtensions: ['.svelte'],
+  },
+  env: {
+    browser: true,
+    es2017: true,
+    node: true,
+  },
+  overrides: [
+    {
+      files: ['*.svelte'],
+      parser: 'svelte-eslint-parser',
+      parserOptions: {
+        parser: '@typescript-eslint/parser',
+      },
+    },
+  ],
+  globals: {
+    NodeJS: true,
+  },
+  rules: {
+    '@typescript-eslint/no-unused-vars': [
+      'warn',
+      {
+        // Allow underscore (_) variables
+        argsIgnorePattern: '^_$',
+        varsIgnorePattern: '^_$',
+      },
+    ],
+  },
 };
diff --git a/web/.prettierrc b/web/.prettierrc
index ff2677efde..a095d3a516 100644
--- a/web/.prettierrc
+++ b/web/.prettierrc
@@ -1,6 +1,7 @@
 {
-	"useTabs": true,
-	"singleQuote": true,
-	"trailingComma": "none",
-	"printWidth": 100
+  "singleQuote": true,
+  "trailingComma": "all",
+  "printWidth": 120,
+  "semi": true,
+  "organizeImportsSkipDestructiveCodeActions": true
 }
diff --git a/web/__mocks__/$app/environment.js b/web/__mocks__/$app/environment.js
index 357e6533cd..453d98b84f 100644
--- a/web/__mocks__/$app/environment.js
+++ b/web/__mocks__/$app/environment.js
@@ -1,3 +1,3 @@
 module.exports = {
-	browser: false
+  browser: false,
 };
diff --git a/web/__mocks__/$env/dynamic/public.js b/web/__mocks__/$env/dynamic/public.js
index 643ba805ff..910f4005a4 100644
--- a/web/__mocks__/$env/dynamic/public.js
+++ b/web/__mocks__/$env/dynamic/public.js
@@ -1,3 +1,3 @@
 module.exports = {
-	env: {}
+  env: {},
 };
diff --git a/web/babel.config.cjs b/web/babel.config.cjs
index 96a36d611a..e6ffbd417e 100644
--- a/web/babel.config.cjs
+++ b/web/babel.config.cjs
@@ -1,3 +1,3 @@
 module.exports = {
-	presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript']
+  presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
 };
diff --git a/web/jest.config.mjs b/web/jest.config.mjs
index 9ba08b74b1..5eeb60f956 100644
--- a/web/jest.config.mjs
+++ b/web/jest.config.mjs
@@ -4,200 +4,199 @@
  */
 
 export default {
-	// All imported modules in your tests should be mocked automatically
-	// automock: false,
+  // All imported modules in your tests should be mocked automatically
+  // automock: false,
 
-	// Stop running tests after `n` failures
-	// bail: 0,
+  // Stop running tests after `n` failures
+  // bail: 0,
 
-	// The directory where Jest should store its cached dependency information
-	// cacheDirectory: "/private/var/folders/6n/31wm28711gzbt3gzsxhzxx500000gn/T/jest_dx",
+  // The directory where Jest should store its cached dependency information
+  // cacheDirectory: "/private/var/folders/6n/31wm28711gzbt3gzsxhzxx500000gn/T/jest_dx",
 
-	// Automatically clear mock calls, instances, contexts and results before every test
-	clearMocks: true,
+  // Automatically clear mock calls, instances, contexts and results before every test
+  clearMocks: true,
 
-	// Indicates whether the coverage information should be collected while executing the test
-	// collectCoverage: false,
+  // Indicates whether the coverage information should be collected while executing the test
+  // collectCoverage: false,
 
-	// An array of glob patterns indicating a set of files for which coverage information should be collected
-	collectCoverageFrom: ['src/**/*.*', '!src/api/open-api/**'],
+  // An array of glob patterns indicating a set of files for which coverage information should be collected
+  collectCoverageFrom: ['src/**/*.*', '!src/api/open-api/**'],
 
-	// The directory where Jest should output its coverage files
-	// coverageDirectory: undefined,
-	coverageThreshold: {
-		global: {
-			lines: 4,
-			statements: 4
-		}
-	},
+  // The directory where Jest should output its coverage files
+  // coverageDirectory: undefined,
+  coverageThreshold: {
+    global: {
+      lines: 4,
+      statements: 4,
+    },
+  },
 
-	// An array of regexp pattern strings used to skip coverage collection
-	// coveragePathIgnorePatterns: [
-	//   "/node_modules/"
-	// ],
+  // An array of regexp pattern strings used to skip coverage collection
+  // coveragePathIgnorePatterns: [
+  //   "/node_modules/"
+  // ],
 
-	// Indicates which provider should be used to instrument code for coverage
-	coverageProvider: 'v8',
+  // Indicates which provider should be used to instrument code for coverage
+  coverageProvider: 'v8',
 
-	// A list of reporter names that Jest uses when writing coverage reports
-	// coverageReporters: [
-	//   "json",
-	//   "text",
-	//   "lcov",
-	//   "clover"
-	// ],
+  // A list of reporter names that Jest uses when writing coverage reports
+  // coverageReporters: [
+  //   "json",
+  //   "text",
+  //   "lcov",
+  //   "clover"
+  // ],
 
-	// An object that configures minimum threshold enforcement for coverage results
-	// coverageThreshold: undefined,
+  // An object that configures minimum threshold enforcement for coverage results
+  // coverageThreshold: undefined,
 
-	// A path to a custom dependency extractor
-	// dependencyExtractor: undefined,
+  // A path to a custom dependency extractor
+  // dependencyExtractor: undefined,
 
-	// Make calling deprecated APIs throw helpful error messages
-	// errorOnDeprecated: false,
+  // Make calling deprecated APIs throw helpful error messages
+  // errorOnDeprecated: false,
 
-	// The default configuration for fake timers
-	// fakeTimers: {
-	//   "enableGlobally": false
-	// },
+  // The default configuration for fake timers
+  // fakeTimers: {
+  //   "enableGlobally": false
+  // },
 
-	// Force coverage collection from ignored files using an array of glob patterns
-	// forceCoverageMatch: [],
+  // Force coverage collection from ignored files using an array of glob patterns
+  // forceCoverageMatch: [],
 
-	// A path to a module which exports an async function that is triggered once before all test suites
-	// globalSetup: undefined,
+  // A path to a module which exports an async function that is triggered once before all test suites
+  // globalSetup: undefined,
 
-	// A path to a module which exports an async function that is triggered once after all test suites
-	// globalTeardown: undefined,
+  // A path to a module which exports an async function that is triggered once after all test suites
+  // globalTeardown: undefined,
 
-	// A set of global variables that need to be available in all test environments
-	// globals: {},
+  // A set of global variables that need to be available in all test environments
+  // globals: {},
 
-	// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
-	// maxWorkers: "50%",
+  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
+  // maxWorkers: "50%",
 
-	// An array of directory names to be searched recursively up from the requiring module's location
-	// moduleDirectories: [
-	//   "node_modules"
-	// ],
+  // An array of directory names to be searched recursively up from the requiring module's location
+  // moduleDirectories: [
+  //   "node_modules"
+  // ],
 
-	// An array of file extensions your modules use
-	moduleFileExtensions: ['svelte', 'js', 'ts'],
+  // An array of file extensions your modules use
+  moduleFileExtensions: ['svelte', 'js', 'ts'],
 
-	// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
-	moduleNameMapper: {
-		'\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
-			'identity-obj-proxy',
-		'^\\$lib(.*)$': '<rootDir>/src/lib$1',
-		'^\\@api(.*)$': '<rootDir>/src/api$1',
-		'^\\@test-data(.*)$': '<rootDir>/src/test-data$1'
-	},
+  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+  moduleNameMapper: {
+    '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'identity-obj-proxy',
+    '^\\$lib(.*)$': '<rootDir>/src/lib$1',
+    '^\\@api(.*)$': '<rootDir>/src/api$1',
+    '^\\@test-data(.*)$': '<rootDir>/src/test-data$1',
+  },
 
-	// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
-	// modulePathIgnorePatterns: [],
+  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+  // modulePathIgnorePatterns: [],
 
-	// Activates notifications for test results
-	// notify: false,
+  // Activates notifications for test results
+  // notify: false,
 
-	// An enum that specifies notification mode. Requires { notify: true }
-	// notifyMode: "failure-change",
+  // An enum that specifies notification mode. Requires { notify: true }
+  // notifyMode: "failure-change",
 
-	// A preset that is used as a base for Jest's configuration
-	// preset: undefined,
+  // A preset that is used as a base for Jest's configuration
+  // preset: undefined,
 
-	// Run tests from one or more projects
-	// projects: undefined,
+  // Run tests from one or more projects
+  // projects: undefined,
 
-	// Use this configuration option to add custom reporters to Jest
-	// reporters: undefined,
+  // Use this configuration option to add custom reporters to Jest
+  // reporters: undefined,
 
-	// Automatically reset mock state before every test
-	// resetMocks: false,
+  // Automatically reset mock state before every test
+  // resetMocks: false,
 
-	// Reset the module registry before running each individual test
-	// resetModules: false,
+  // Reset the module registry before running each individual test
+  // resetModules: false,
 
-	// A path to a custom resolver
-	// resolver: undefined,
+  // A path to a custom resolver
+  // resolver: undefined,
 
-	// Automatically restore mock state and implementation before every test
-	// restoreMocks: false,
+  // Automatically restore mock state and implementation before every test
+  // restoreMocks: false,
 
-	// The root directory that Jest should scan for tests and modules within
-	// rootDir: undefined,
+  // The root directory that Jest should scan for tests and modules within
+  // rootDir: undefined,
 
-	// A list of paths to directories that Jest should use to search for files in
-	// roots: [
-	//   "<rootDir>"
-	// ],
+  // A list of paths to directories that Jest should use to search for files in
+  // roots: [
+  //   "<rootDir>"
+  // ],
 
-	// Allows you to use a custom runner instead of Jest's default test runner
-	// runner: "jest-runner",
+  // Allows you to use a custom runner instead of Jest's default test runner
+  // runner: "jest-runner",
 
-	// The paths to modules that run some code to configure or set up the testing environment before each test
-	// setupFiles: [],
+  // The paths to modules that run some code to configure or set up the testing environment before each test
+  // setupFiles: [],
 
-	// A list of paths to modules that run some code to configure or set up the testing framework before each test
-	// setupFilesAfterEnv: [],
+  // A list of paths to modules that run some code to configure or set up the testing framework before each test
+  // setupFilesAfterEnv: [],
 
-	// The number of seconds after which a test is considered as slow and reported as such in the results.
-	// slowTestThreshold: 5,
+  // The number of seconds after which a test is considered as slow and reported as such in the results.
+  // slowTestThreshold: 5,
 
-	// A list of paths to snapshot serializer modules Jest should use for snapshot testing
-	// snapshotSerializers: [],
+  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+  // snapshotSerializers: [],
 
-	// The test environment that will be used for testing
-	testEnvironment: 'jsdom',
+  // The test environment that will be used for testing
+  testEnvironment: 'jsdom',
 
-	// Options that will be passed to the testEnvironment
-	// testEnvironmentOptions: {},
+  // Options that will be passed to the testEnvironment
+  // testEnvironmentOptions: {},
 
-	// Adds a location field to test results
-	// testLocationInResults: false,
+  // Adds a location field to test results
+  // testLocationInResults: false,
 
-	// The glob patterns Jest uses to detect test files
-	// testMatch: [
-	//   "**/__tests__/**/*.[jt]s?(x)",
-	//   "**/?(*.)+(spec|test).[tj]s?(x)"
-	// ],
+  // The glob patterns Jest uses to detect test files
+  // testMatch: [
+  //   "**/__tests__/**/*.[jt]s?(x)",
+  //   "**/?(*.)+(spec|test).[tj]s?(x)"
+  // ],
 
-	// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
-	// testPathIgnorePatterns: [
-	//   "/node_modules/"
-	// ],
+  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+  // testPathIgnorePatterns: [
+  //   "/node_modules/"
+  // ],
 
-	// The regexp pattern or array of patterns that Jest uses to detect test files
-	// testRegex: [],
+  // The regexp pattern or array of patterns that Jest uses to detect test files
+  // testRegex: [],
 
-	// This option allows the use of a custom results processor
-	// testResultsProcessor: undefined,
+  // This option allows the use of a custom results processor
+  // testResultsProcessor: undefined,
 
-	// This option allows use of a custom test runner
-	// testRunner: "jest-circus/runner",
+  // This option allows use of a custom test runner
+  // testRunner: "jest-circus/runner",
 
-	// A map from regular expressions to paths to transformers
-	transform: {
-		'\\.[jt]sx?$': 'babel-jest',
-		'^.+\\.svelte$': [
-			'svelte-jester',
-			{
-				preprocess: true
-			}
-		]
-	},
+  // A map from regular expressions to paths to transformers
+  transform: {
+    '\\.[jt]sx?$': 'babel-jest',
+    '^.+\\.svelte$': [
+      'svelte-jester',
+      {
+        preprocess: true,
+      },
+    ],
+  },
 
-	// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
-	transformIgnorePatterns: ['/node_modules/(?!svelte-material-icons).*/', '\\.pnp\\.[^\\/]+$']
+  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+  transformIgnorePatterns: ['/node_modules/(?!svelte-material-icons).*/', '\\.pnp\\.[^\\/]+$'],
 
-	// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
-	// unmockedModulePathPatterns: undefined,
+  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+  // unmockedModulePathPatterns: undefined,
 
-	// Indicates whether each individual test should be reported during the run
-	// verbose: undefined,
+  // Indicates whether each individual test should be reported during the run
+  // verbose: undefined,
 
-	// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
-	// watchPathIgnorePatterns: [],
+  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+  // watchPathIgnorePatterns: [],
 
-	// Whether to use watchman for file crawling
-	// watchman: true,
+  // Whether to use watchman for file crawling
+  // watchman: true,
 };
diff --git a/web/postcss.config.cjs b/web/postcss.config.cjs
index 054c147cbf..12a703d900 100644
--- a/web/postcss.config.cjs
+++ b/web/postcss.config.cjs
@@ -1,6 +1,6 @@
 module.exports = {
-	plugins: {
-		tailwindcss: {},
-		autoprefixer: {}
-	}
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
 };
diff --git a/web/src/api/api.ts b/web/src/api/api.ts
index 2924d717d1..22f9989721 100644
--- a/web/src/api/api.ts
+++ b/web/src/api/api.ts
@@ -1,129 +1,125 @@
 import {
-	AlbumApi,
-	APIKeyApi,
-	AssetApi,
-	AssetApiFp,
-	AuthenticationApi,
-	Configuration,
-	ConfigurationParameters,
-	JobApi,
-	JobName,
-	OAuthApi,
-	PartnerApi,
-	PersonApi,
-	SearchApi,
-	ServerInfoApi,
-	SharedLinkApi,
-	SystemConfigApi,
-	UserApi,
-	UserApiFp
+  AlbumApi,
+  APIKeyApi,
+  AssetApi,
+  AssetApiFp,
+  AuthenticationApi,
+  Configuration,
+  ConfigurationParameters,
+  JobApi,
+  JobName,
+  OAuthApi,
+  PartnerApi,
+  PersonApi,
+  SearchApi,
+  ServerInfoApi,
+  SharedLinkApi,
+  SystemConfigApi,
+  UserApi,
+  UserApiFp,
 } from './open-api';
 import { BASE_PATH } from './open-api/base';
 import { DUMMY_BASE_URL, toPathString } from './open-api/common';
 import type { ApiParams } from './types';
 
 export class ImmichApi {
-	public albumApi: AlbumApi;
-	public assetApi: AssetApi;
-	public authenticationApi: AuthenticationApi;
-	public jobApi: JobApi;
-	public keyApi: APIKeyApi;
-	public oauthApi: OAuthApi;
-	public partnerApi: PartnerApi;
-	public searchApi: SearchApi;
-	public serverInfoApi: ServerInfoApi;
-	public sharedLinkApi: SharedLinkApi;
-	public personApi: PersonApi;
-	public systemConfigApi: SystemConfigApi;
-	public userApi: UserApi;
+  public albumApi: AlbumApi;
+  public assetApi: AssetApi;
+  public authenticationApi: AuthenticationApi;
+  public jobApi: JobApi;
+  public keyApi: APIKeyApi;
+  public oauthApi: OAuthApi;
+  public partnerApi: PartnerApi;
+  public searchApi: SearchApi;
+  public serverInfoApi: ServerInfoApi;
+  public sharedLinkApi: SharedLinkApi;
+  public personApi: PersonApi;
+  public systemConfigApi: SystemConfigApi;
+  public userApi: UserApi;
 
-	private config: Configuration;
+  private config: Configuration;
 
-	constructor(params: ConfigurationParameters) {
-		this.config = new Configuration(params);
+  constructor(params: ConfigurationParameters) {
+    this.config = new Configuration(params);
 
-		this.albumApi = new AlbumApi(this.config);
-		this.assetApi = new AssetApi(this.config);
-		this.authenticationApi = new AuthenticationApi(this.config);
-		this.jobApi = new JobApi(this.config);
-		this.keyApi = new APIKeyApi(this.config);
-		this.oauthApi = new OAuthApi(this.config);
-		this.partnerApi = new PartnerApi(this.config);
-		this.searchApi = new SearchApi(this.config);
-		this.serverInfoApi = new ServerInfoApi(this.config);
-		this.sharedLinkApi = new SharedLinkApi(this.config);
-		this.personApi = new PersonApi(this.config);
-		this.systemConfigApi = new SystemConfigApi(this.config);
-		this.userApi = new UserApi(this.config);
-	}
+    this.albumApi = new AlbumApi(this.config);
+    this.assetApi = new AssetApi(this.config);
+    this.authenticationApi = new AuthenticationApi(this.config);
+    this.jobApi = new JobApi(this.config);
+    this.keyApi = new APIKeyApi(this.config);
+    this.oauthApi = new OAuthApi(this.config);
+    this.partnerApi = new PartnerApi(this.config);
+    this.searchApi = new SearchApi(this.config);
+    this.serverInfoApi = new ServerInfoApi(this.config);
+    this.sharedLinkApi = new SharedLinkApi(this.config);
+    this.personApi = new PersonApi(this.config);
+    this.systemConfigApi = new SystemConfigApi(this.config);
+    this.userApi = new UserApi(this.config);
+  }
 
-	private createUrl(path: string, params?: Record<string, unknown>) {
-		const searchParams = new URLSearchParams();
-		for (const key in params) {
-			const value = params[key];
-			if (value !== undefined && value !== null) {
-				searchParams.set(key, value.toString());
-			}
-		}
+  private createUrl(path: string, params?: Record<string, unknown>) {
+    const searchParams = new URLSearchParams();
+    for (const key in params) {
+      const value = params[key];
+      if (value !== undefined && value !== null) {
+        searchParams.set(key, value.toString());
+      }
+    }
 
-		const url = new URL(path, DUMMY_BASE_URL);
-		url.search = searchParams.toString();
+    const url = new URL(path, DUMMY_BASE_URL);
+    url.search = searchParams.toString();
 
-		return (this.config.basePath || BASE_PATH) + toPathString(url);
-	}
+    return (this.config.basePath || BASE_PATH) + toPathString(url);
+  }
 
-	public setAccessToken(accessToken: string) {
-		this.config.accessToken = accessToken;
-	}
+  public setAccessToken(accessToken: string) {
+    this.config.accessToken = accessToken;
+  }
 
-	public removeAccessToken() {
-		this.config.accessToken = undefined;
-	}
+  public removeAccessToken() {
+    this.config.accessToken = undefined;
+  }
 
-	public setBaseUrl(baseUrl: string) {
-		this.config.basePath = baseUrl;
-	}
+  public setBaseUrl(baseUrl: string) {
+    this.config.basePath = baseUrl;
+  }
 
-	public getAssetFileUrl(
-		...[assetId, isThumb, isWeb, key]: ApiParams<typeof AssetApiFp, 'serveFile'>
-	) {
-		const path = `/asset/file/${assetId}`;
-		return this.createUrl(path, { isThumb, isWeb, key });
-	}
+  public getAssetFileUrl(...[assetId, isThumb, isWeb, key]: ApiParams<typeof AssetApiFp, 'serveFile'>) {
+    const path = `/asset/file/${assetId}`;
+    return this.createUrl(path, { isThumb, isWeb, key });
+  }
 
-	public getAssetThumbnailUrl(
-		...[assetId, format, key]: ApiParams<typeof AssetApiFp, 'getAssetThumbnail'>
-	) {
-		const path = `/asset/thumbnail/${assetId}`;
-		return this.createUrl(path, { format, key });
-	}
+  public getAssetThumbnailUrl(...[assetId, format, key]: ApiParams<typeof AssetApiFp, 'getAssetThumbnail'>) {
+    const path = `/asset/thumbnail/${assetId}`;
+    return this.createUrl(path, { format, key });
+  }
 
-	public getProfileImageUrl(...[userId]: ApiParams<typeof UserApiFp, 'getProfileImage'>) {
-		const path = `/user/profile-image/${userId}`;
-		return this.createUrl(path);
-	}
+  public getProfileImageUrl(...[userId]: ApiParams<typeof UserApiFp, 'getProfileImage'>) {
+    const path = `/user/profile-image/${userId}`;
+    return this.createUrl(path);
+  }
 
-	public getPeopleThumbnailUrl(personId: string) {
-		const path = `/person/${personId}/thumbnail`;
-		return this.createUrl(path);
-	}
+  public getPeopleThumbnailUrl(personId: string) {
+    const path = `/person/${personId}/thumbnail`;
+    return this.createUrl(path);
+  }
 
-	public getJobName(jobName: JobName) {
-		const names: Record<JobName, string> = {
-			[JobName.ThumbnailGeneration]: 'Generate Thumbnails',
-			[JobName.MetadataExtraction]: 'Extract Metadata',
-			[JobName.Sidecar]: 'Sidecar Metadata',
-			[JobName.ObjectTagging]: 'Tag Objects',
-			[JobName.ClipEncoding]: 'Encode Clip',
-			[JobName.RecognizeFaces]: 'Recognize Faces',
-			[JobName.VideoConversion]: 'Transcode Videos',
-			[JobName.StorageTemplateMigration]: 'Storage Template Migration',
-			[JobName.BackgroundTask]: 'Background Tasks',
-			[JobName.Search]: 'Search'
-		};
+  public getJobName(jobName: JobName) {
+    const names: Record<JobName, string> = {
+      [JobName.ThumbnailGeneration]: 'Generate Thumbnails',
+      [JobName.MetadataExtraction]: 'Extract Metadata',
+      [JobName.Sidecar]: 'Sidecar Metadata',
+      [JobName.ObjectTagging]: 'Tag Objects',
+      [JobName.ClipEncoding]: 'Encode Clip',
+      [JobName.RecognizeFaces]: 'Recognize Faces',
+      [JobName.VideoConversion]: 'Transcode Videos',
+      [JobName.StorageTemplateMigration]: 'Storage Template Migration',
+      [JobName.BackgroundTask]: 'Background Tasks',
+      [JobName.Search]: 'Search',
+    };
 
-		return names[jobName];
-	}
+    return names[jobName];
+  }
 }
 
 export const api = new ImmichApi({ basePath: '/api' });
diff --git a/web/src/api/index.ts b/web/src/api/index.ts
index cf4e108bdb..1a16e38b56 100644
--- a/web/src/api/index.ts
+++ b/web/src/api/index.ts
@@ -1,3 +1,3 @@
-export * from './open-api';
 export * from './api';
+export * from './open-api';
 export * from './utils';
diff --git a/web/src/api/types.ts b/web/src/api/types.ts
index 0bb28cca88..b26f26484d 100644
--- a/web/src/api/types.ts
+++ b/web/src/api/types.ts
@@ -3,10 +3,6 @@ import type { Configuration } from './open-api';
 /* eslint-disable @typescript-eslint/no-explicit-any */
 export type ApiFp = (configuration: Configuration) => Record<any, (...args: any) => any>;
 
-export type OmitLast<T extends readonly unknown[]> = T extends readonly [...infer U, any?]
-	? U
-	: [...T];
+export type OmitLast<T extends readonly unknown[]> = T extends readonly [...infer U, any?] ? U : [...T];
 
-export type ApiParams<F extends ApiFp, K extends keyof ReturnType<F>> = OmitLast<
-	Parameters<ReturnType<F>[K]>
->;
+export type ApiParams<F extends ApiFp, K extends keyof ReturnType<F>> = OmitLast<Parameters<ReturnType<F>[K]>>;
diff --git a/web/src/api/utils.ts b/web/src/api/utils.ts
index 39c319901f..75f3b47863 100644
--- a/web/src/api/utils.ts
+++ b/web/src/api/utils.ts
@@ -5,31 +5,31 @@ import type { UserResponseDto } from './open-api';
 export type ApiError = AxiosError<{ message: string }>;
 
 export const oauth = {
-	isCallback: (location: Location) => {
-		const search = location.search;
-		return search.includes('code=') || search.includes('error=');
-	},
-	isAutoLaunchDisabled: (location: Location) => {
-		const values = ['autoLaunch=0', 'password=1', 'password=true'];
-		for (const value of values) {
-			if (location.search.includes(value)) {
-				return true;
-			}
-		}
-		return false;
-	},
-	getConfig: (location: Location) => {
-		const redirectUri = location.href.split('?')[0];
-		console.log(`OAuth Redirect URI: ${redirectUri}`);
-		return api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri } });
-	},
-	login: (location: Location) => {
-		return api.oauthApi.callback({ oAuthCallbackDto: { url: location.href } });
-	},
-	link: (location: Location): AxiosPromise<UserResponseDto> => {
-		return api.oauthApi.link({ oAuthCallbackDto: { url: location.href } });
-	},
-	unlink: () => {
-		return api.oauthApi.unlink();
-	}
+  isCallback: (location: Location) => {
+    const search = location.search;
+    return search.includes('code=') || search.includes('error=');
+  },
+  isAutoLaunchDisabled: (location: Location) => {
+    const values = ['autoLaunch=0', 'password=1', 'password=true'];
+    for (const value of values) {
+      if (location.search.includes(value)) {
+        return true;
+      }
+    }
+    return false;
+  },
+  getConfig: (location: Location) => {
+    const redirectUri = location.href.split('?')[0];
+    console.log(`OAuth Redirect URI: ${redirectUri}`);
+    return api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri } });
+  },
+  login: (location: Location) => {
+    return api.oauthApi.callback({ oAuthCallbackDto: { url: location.href } });
+  },
+  link: (location: Location): AxiosPromise<UserResponseDto> => {
+    return api.oauthApi.link({ oAuthCallbackDto: { url: location.href } });
+  },
+  unlink: () => {
+    return api.oauthApi.unlink();
+  },
 };
diff --git a/web/src/app.css b/web/src/app.css
index 3802a4b258..f959d2dc88 100644
--- a/web/src/app.css
+++ b/web/src/app.css
@@ -3,93 +3,93 @@
 @tailwind utilities;
 
 @font-face {
-	font-family: 'Work Sans';
-	src: url('$lib/assets/fonts/WorkSans-VariableFont_wght.ttf') format('truetype-variations');
-	font-weight: 1 999;
+  font-family: 'Work Sans';
+  src: url('$lib/assets/fonts/WorkSans-VariableFont_wght.ttf') format('truetype-variations');
+  font-weight: 1 999;
 }
 
 @font-face {
-	font-family: 'Snowburst One';
-	src: url('$lib/assets/fonts/SnowburstOne-Regular.ttf') format('truetype');
+  font-family: 'Snowburst One';
+  src: url('$lib/assets/fonts/SnowburstOne-Regular.ttf') format('truetype');
 }
 
 :root {
-	font-family: 'Work Sans', sans-serif;
+  font-family: 'Work Sans', sans-serif;
 }
 
 html {
-	height: 100%;
-	width: 100%;
+  height: 100%;
+  width: 100%;
 }
 
 html::-webkit-scrollbar {
-	width: 8px;
+  width: 8px;
 }
 
 /* Track */
 html::-webkit-scrollbar-track {
-	background: #f1f1f1;
-	border-radius: 16px;
+  background: #f1f1f1;
+  border-radius: 16px;
 }
 
 /* Handle */
 html::-webkit-scrollbar-thumb {
-	background: rgba(85, 86, 87, 0.408);
-	border-radius: 16px;
+  background: rgba(85, 86, 87, 0.408);
+  border-radius: 16px;
 }
 
 /* Handle on hover */
 html::-webkit-scrollbar-thumb:hover {
-	background: #4250afad;
-	border-radius: 16px;
+  background: #4250afad;
+  border-radius: 16px;
 }
 
 body {
-	margin: 0;
-	color: #5f6368;
+  margin: 0;
+  color: #5f6368;
 }
 
 input:focus-visible {
-	outline-offset: 0px !important;
-	outline: none !important;
+  outline-offset: 0px !important;
+  outline: none !important;
 }
 
 @layer utilities {
-	.immich-form-input {
-		@apply bg-slate-200 p-4 rounded-xl focus:border-immich-primary text-sm dark:bg-gray-600 dark:text-immich-dark-fg disabled:bg-gray-400 dark:disabled:bg-gray-800 disabled:cursor-not-allowed disabled:text-gray-200;
-	}
+  .immich-form-input {
+    @apply bg-slate-200 p-4 rounded-xl focus:border-immich-primary text-sm dark:bg-gray-600 dark:text-immich-dark-fg disabled:bg-gray-400 dark:disabled:bg-gray-800 disabled:cursor-not-allowed disabled:text-gray-200;
+  }
 
-	.immich-form-label {
-		@apply font-medium text-gray-500 dark:text-gray-300;
-	}
+  .immich-form-label {
+    @apply font-medium text-gray-500 dark:text-gray-300;
+  }
 
-	/* width */
-	.immich-scrollbar::-webkit-scrollbar {
-		width: 8px;
-	}
+  /* width */
+  .immich-scrollbar::-webkit-scrollbar {
+    width: 8px;
+  }
 
-	/* Track */
-	.immich-scrollbar::-webkit-scrollbar-track {
-		background: #f1f1f1;
-		border-radius: 16px;
-	}
+  /* 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 */
+  .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;
-	}
+  /* Handle on hover */
+  .immich-scrollbar::-webkit-scrollbar-thumb:hover {
+    background: #4250afad;
+    border-radius: 16px;
+  }
 
-	/* Hidden scrollbar */
-	/* width */
-	.scrollbar-hidden::-webkit-scrollbar {
-		display: none;
-		scrollbar-width: none;
-	}
+  /* Hidden scrollbar */
+  /* width */
+  .scrollbar-hidden::-webkit-scrollbar {
+    display: none;
+    scrollbar-width: none;
+  }
 }
diff --git a/web/src/app.d.ts b/web/src/app.d.ts
index e6bff3ed83..c29b346beb 100644
--- a/web/src/app.d.ts
+++ b/web/src/app.d.ts
@@ -3,32 +3,32 @@
 // See https://kit.svelte.dev/docs/types#app
 // for information about these interfaces
 declare namespace App {
-	interface Locals {
-		user?: import('@api').UserResponseDto;
-		api: import('@api').ImmichApi;
-	}
+  interface Locals {
+    user?: import('@api').UserResponseDto;
+    api: import('@api').ImmichApi;
+  }
 
-	interface PageData {
-		meta: {
-			title: string;
-			description?: string;
-			imageUrl?: string;
-		};
-	}
+  interface PageData {
+    meta: {
+      title: string;
+      description?: string;
+      imageUrl?: string;
+    };
+  }
 
-	interface Error {
-		message: string;
-		stack?: string;
-		code?: string | number;
-	}
+  interface Error {
+    message: string;
+    stack?: string;
+    code?: string | number;
+  }
 }
 
 // Source: https://stackoverflow.com/questions/63814432/typescript-typing-of-non-standard-window-event-in-svelte
 // To fix the <svelte:window... in components/asset-viewer/photo-viewer.svelte
 declare namespace svelteHTML {
-	// eslint-disable-next-line @typescript-eslint/no-unused-vars
-	interface HTMLAttributes<T> {
-		'on:copyImage'?: () => void;
-		'on:zoomImage'?: () => void;
-	}
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  interface HTMLAttributes<T> {
+    'on:copyImage'?: () => void;
+    'on:zoomImage'?: () => void;
+  }
 }
diff --git a/web/src/app.html b/web/src/app.html
index 8aed10653b..9da6868f7d 100644
--- a/web/src/app.html
+++ b/web/src/app.html
@@ -1,21 +1,21 @@
 <!DOCTYPE html>
 <html lang="en" class="dark">
-	<head>
-		<meta charset="utf-8" />
-		<meta name="viewport" content="width=device-width, initial-scale=1" />
-		%sveltekit.head%
-		<script>
-			/**
-			 * Prevent FOUC on page load.
-			 */
-			const theme = localStorage.getItem('color-theme') || 'dark';
-			if (theme === 'light') {
-				document.documentElement.classList.remove('dark');
-			}
-		</script>
-	</head>
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    %sveltekit.head%
+    <script>
+      /**
+       * Prevent FOUC on page load.
+       */
+      const theme = localStorage.getItem('color-theme') || 'dark';
+      if (theme === 'light') {
+        document.documentElement.classList.remove('dark');
+      }
+    </script>
+  </head>
 
-	<body class="bg-immich-bg dark:bg-immich-dark-bg">
-		<div>%sveltekit.body%</div>
-	</body>
+  <body class="bg-immich-bg dark:bg-immich-dark-bg">
+    <div>%sveltekit.body%</div>
+  </body>
 </html>
diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts
index 8998257e75..6abd4a2f21 100644
--- a/web/src/hooks.server.ts
+++ b/web/src/hooks.server.ts
@@ -4,54 +4,54 @@ import type { AxiosError, AxiosResponse } from 'axios';
 import { ImmichApi } from './api/api';
 
 export const handle = (async ({ event, resolve }) => {
-	const basePath = env.PUBLIC_IMMICH_SERVER_URL || 'http://immich-server:3001';
-	const accessToken = event.cookies.get('immich_access_token');
-	const api = new ImmichApi({ basePath, accessToken });
+  const basePath = env.PUBLIC_IMMICH_SERVER_URL || 'http://immich-server:3001';
+  const accessToken = event.cookies.get('immich_access_token');
+  const api = new ImmichApi({ basePath, accessToken });
 
-	// API instance that should be used for all server-side requests.
-	event.locals.api = api;
+  // API instance that should be used for all server-side requests.
+  event.locals.api = api;
 
-	if (accessToken) {
-		try {
-			const { data: user } = await api.userApi.getMyUserInfo();
-			event.locals.user = user;
-		} catch (err) {
-			const apiError = err as AxiosError;
+  if (accessToken) {
+    try {
+      const { data: user } = await api.userApi.getMyUserInfo();
+      event.locals.user = user;
+    } catch (err) {
+      const apiError = err as AxiosError;
 
-			// Ignore 401 unauthorized errors and log all others.
-			if (apiError.response?.status !== 401) {
-				console.error('[ERROR] hooks.server.ts [handle]:', err);
-			}
-		}
-	}
+      // Ignore 401 unauthorized errors and log all others.
+      if (apiError.response?.status !== 401) {
+        console.error('[ERROR] hooks.server.ts [handle]:', err);
+      }
+    }
+  }
 
-	const res = await resolve(event);
+  const res = await resolve(event);
 
-	// The link header can grow quite big and has caused issues with our nginx
-	// proxy returning a 502 Bad Gateway error. Therefore the header gets deleted.
-	res.headers.delete('Link');
+  // The link header can grow quite big and has caused issues with our nginx
+  // proxy returning a 502 Bad Gateway error. Therefore the header gets deleted.
+  res.headers.delete('Link');
 
-	return res;
+  return res;
 }) satisfies Handle;
 
 const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?';
 
 export const handleError: HandleServerError = async ({ error }) => {
-	const httpError = error as AxiosError;
-	const response = httpError?.response as AxiosResponse<{
-		message: string;
-		statusCode: number;
-		error: string;
-	}>;
+  const httpError = error as AxiosError;
+  const response = httpError?.response as AxiosResponse<{
+    message: string;
+    statusCode: number;
+    error: string;
+  }>;
 
-	let code = response?.data?.statusCode || response?.status || httpError.code || '500';
-	if (response) {
-		code += ` - ${response.data?.error || response.statusText}`;
-	}
+  let code = response?.data?.statusCode || response?.status || httpError.code || '500';
+  if (response) {
+    code += ` - ${response.data?.error || response.statusText}`;
+  }
 
-	return {
-		message: response?.data?.message || httpError?.message || DEFAULT_MESSAGE,
-		code,
-		stack: httpError?.stack
-	};
+  return {
+    message: response?.data?.message || httpError?.message || DEFAULT_MESSAGE,
+    code,
+    stack: httpError?.stack,
+  };
 };
diff --git a/web/src/lib/__mocks__/jsdom-url.mock.ts b/web/src/lib/__mocks__/jsdom-url.mock.ts
index b915c8438b..abe00cd2ed 100644
--- a/web/src/lib/__mocks__/jsdom-url.mock.ts
+++ b/web/src/lib/__mocks__/jsdom-url.mock.ts
@@ -1,8 +1,8 @@
 const createObjectURLMock = jest.fn();
 
 Object.defineProperty(URL, 'createObjectURL', {
-	writable: true,
-	value: createObjectURLMock
+  writable: true,
+  value: createObjectURLMock,
 });
 
 export { createObjectURLMock };
diff --git a/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte b/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte
index 8f687f3b66..2e703de8ee 100644
--- a/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte
+++ b/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte
@@ -1,36 +1,35 @@
 <script lang="ts">
-	import { api, UserResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
-	import { handleError } from '../../utils/handle-error';
+  import { api, UserResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import { handleError } from '../../utils/handle-error';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	const deleteUser = async () => {
-		try {
-			const deletedUser = await api.userApi.deleteUser({ userId: user.id });
-			if (deletedUser.data.deletedAt != null) {
-				dispatch('user-delete-success');
-			} else {
-				dispatch('user-delete-fail');
-			}
-		} catch (error) {
-			handleError(error, 'Unable to delete user');
-			dispatch('user-delete-fail');
-		}
-	};
+  const deleteUser = async () => {
+    try {
+      const deletedUser = await api.userApi.deleteUser({ userId: user.id });
+      if (deletedUser.data.deletedAt != null) {
+        dispatch('user-delete-success');
+      } else {
+        dispatch('user-delete-fail');
+      }
+    } catch (error) {
+      handleError(error, 'Unable to delete user');
+      dispatch('user-delete-fail');
+    }
+  };
 </script>
 
 <ConfirmDialogue title="Delete User" confirmText="Delete" on:confirm={deleteUser} on:cancel>
-	<svelte:fragment slot="prompt">
-		<div class="flex flex-col gap-4">
-			<p>
-				<b>{user.firstName} {user.lastName}</b>'s account and assets will be permanently deleted
-				after 7 days.
-			</p>
-			<p>Are you sure you want to continue?</p>
-		</div>
-	</svelte:fragment>
+  <svelte:fragment slot="prompt">
+    <div class="flex flex-col gap-4">
+      <p>
+        <b>{user.firstName} {user.lastName}</b>'s account and assets will be permanently deleted after 7 days.
+      </p>
+      <p>Are you sure you want to continue?</p>
+    </div>
+  </svelte:fragment>
 </ConfirmDialogue>
diff --git a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte
index 42be6c3236..cf12b7c436 100644
--- a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte
+++ b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte
@@ -1,21 +1,21 @@
 <script lang="ts" context="module">
-	export type Colors = 'light-gray' | 'gray';
+  export type Colors = 'light-gray' | 'gray';
 </script>
 
 <script lang="ts">
-	export let color: Colors;
+  export let color: Colors;
 
-	const colorClasses: Record<Colors, string> = {
-		'light-gray': 'bg-gray-300/90 dark:bg-gray-600/90',
-		gray: 'bg-gray-300 dark:bg-gray-600'
-	};
+  const colorClasses: Record<Colors, string> = {
+    'light-gray': 'bg-gray-300/90 dark:bg-gray-600/90',
+    gray: 'bg-gray-300 dark:bg-gray-600',
+  };
 </script>
 
 <button
-	class="h-full w-full py-2 flex gap-2 flex-col place-items-center place-content-center px-8 text-gray-600 transition-colors hover:bg-immich-primary hover:text-white dark:text-gray-200 dark:hover:bg-immich-dark-primary text-xs dark:hover:text-black {colorClasses[
-		color
-	]}"
-	on:click
+  class="h-full w-full py-2 flex gap-2 flex-col place-items-center place-content-center px-8 text-gray-600 transition-colors hover:bg-immich-primary hover:text-white dark:text-gray-200 dark:hover:bg-immich-dark-primary text-xs dark:hover:text-black {colorClasses[
+    color
+  ]}"
+  on:click
 >
-	<slot />
+  <slot />
 </button>
diff --git a/web/src/lib/components/admin-page/jobs/job-tile-status.svelte b/web/src/lib/components/admin-page/jobs/job-tile-status.svelte
index 4f31ad6ffb..67edd30f9b 100644
--- a/web/src/lib/components/admin-page/jobs/job-tile-status.svelte
+++ b/web/src/lib/components/admin-page/jobs/job-tile-status.svelte
@@ -1,16 +1,16 @@
 <script lang="ts" context="module">
-	export type Color = 'success' | 'warning';
+  export type Color = 'success' | 'warning';
 </script>
 
 <script lang="ts">
-	export let color: Color;
+  export let color: Color;
 
-	const colorClasses: Record<Color, string> = {
-		success: 'bg-green-500/70 text-gray-900 dark:bg-green-700/90 dark:text-gray-100',
-		warning: 'bg-orange-400/70 text-gray-900 dark:bg-orange-900 dark:text-gray-100'
-	};
+  const colorClasses: Record<Color, string> = {
+    success: 'bg-green-500/70 text-gray-900 dark:bg-green-700/90 dark:text-gray-100',
+    warning: 'bg-orange-400/70 text-gray-900 dark:bg-orange-900 dark:text-gray-100',
+  };
 </script>
 
 <div class="w-full text-center text-sm p-2 {colorClasses[color]}">
-	<slot />
+  <slot />
 </div>
diff --git a/web/src/lib/components/admin-page/jobs/job-tile.svelte b/web/src/lib/components/admin-page/jobs/job-tile.svelte
index d0043765cc..554415d305 100644
--- a/web/src/lib/components/admin-page/jobs/job-tile.svelte
+++ b/web/src/lib/components/admin-page/jobs/job-tile.svelte
@@ -1,149 +1,141 @@
 <script lang="ts">
-	import type Icon from 'svelte-material-icons/AbTesting.svelte';
-	import SelectionSearch from 'svelte-material-icons/SelectionSearch.svelte';
-	import Play from 'svelte-material-icons/Play.svelte';
-	import Pause from 'svelte-material-icons/Pause.svelte';
-	import FastForward from 'svelte-material-icons/FastForward.svelte';
-	import AllInclusive from 'svelte-material-icons/AllInclusive.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import { locale } from '$lib/stores/preferences.store';
-	import { createEventDispatcher } from 'svelte';
-	import { JobCommand, JobCommandDto, JobCountsDto, QueueStatusDto } from '@api';
-	import Badge from '$lib/components/elements/badge.svelte';
-	import JobTileButton from './job-tile-button.svelte';
-	import JobTileStatus from './job-tile-status.svelte';
+  import type Icon from 'svelte-material-icons/AbTesting.svelte';
+  import SelectionSearch from 'svelte-material-icons/SelectionSearch.svelte';
+  import Play from 'svelte-material-icons/Play.svelte';
+  import Pause from 'svelte-material-icons/Pause.svelte';
+  import FastForward from 'svelte-material-icons/FastForward.svelte';
+  import AllInclusive from 'svelte-material-icons/AllInclusive.svelte';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import { locale } from '$lib/stores/preferences.store';
+  import { createEventDispatcher } from 'svelte';
+  import { JobCommand, JobCommandDto, JobCountsDto, QueueStatusDto } from '@api';
+  import Badge from '$lib/components/elements/badge.svelte';
+  import JobTileButton from './job-tile-button.svelte';
+  import JobTileStatus from './job-tile-status.svelte';
 
-	export let title: string;
-	export let subtitle: string | undefined = undefined;
-	export let jobCounts: JobCountsDto;
-	export let queueStatus: QueueStatusDto;
-	export let allowForceCommand = true;
-	export let icon: typeof Icon;
+  export let title: string;
+  export let subtitle: string | undefined = undefined;
+  export let jobCounts: JobCountsDto;
+  export let queueStatus: QueueStatusDto;
+  export let allowForceCommand = true;
+  export let icon: typeof Icon;
 
-	export let allText: string;
-	export let missingText: string;
+  export let allText: string;
+  export let missingText: string;
 
-	const slots = $$props.$$slots;
+  const slots = $$props.$$slots;
 
-	$: waitingCount = jobCounts.waiting + jobCounts.paused + jobCounts.delayed;
-	$: isIdle = !queueStatus.isActive && !queueStatus.isPaused;
+  $: waitingCount = jobCounts.waiting + jobCounts.paused + jobCounts.delayed;
+  $: isIdle = !queueStatus.isActive && !queueStatus.isPaused;
 
-	const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pr-4 pl-6';
+  const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pr-4 pl-6';
 
-	const dispatch = createEventDispatcher<{ command: JobCommandDto }>();
+  const dispatch = createEventDispatcher<{ command: JobCommandDto }>();
 </script>
 
 <div
-	class="flex sm:flex-row flex-col bg-gray-100 dark:bg-immich-dark-gray rounded-2xl sm:rounded-[35px] overflow-hidden"
+  class="flex sm:flex-row flex-col bg-gray-100 dark:bg-immich-dark-gray rounded-2xl sm:rounded-[35px] overflow-hidden"
 >
-	<div class="flex flex-col w-full">
-		{#if queueStatus.isPaused}
-			<JobTileStatus color="warning">Paused</JobTileStatus>
-		{:else if queueStatus.isActive}
-			<JobTileStatus color="success">Active</JobTileStatus>
-		{/if}
-		<div class="flex flex-col gap-2 p-5 sm:p-7 md:p-9">
-			<div
-				class="flex items-center gap-4 text-xl font-semibold text-immich-primary dark:text-immich-dark-primary"
-			>
-				<span class="flex gap-2 items-center">
-					<svelte:component this={icon} size="1.25em" class="shrink-0 hidden sm:block" />
-					{title.toUpperCase()}
-				</span>
-				<div class="flex gap-2">
-					{#if jobCounts.failed > 0}
-						<Badge color="primary">
-							{jobCounts.failed.toLocaleString($locale)} failed
-						</Badge>
-					{/if}
-					{#if jobCounts.delayed > 0}
-						<Badge color="secondary">
-							{jobCounts.delayed.toLocaleString($locale)} delayed
-						</Badge>
-					{/if}
-				</div>
-			</div>
+  <div class="flex flex-col w-full">
+    {#if queueStatus.isPaused}
+      <JobTileStatus color="warning">Paused</JobTileStatus>
+    {:else if queueStatus.isActive}
+      <JobTileStatus color="success">Active</JobTileStatus>
+    {/if}
+    <div class="flex flex-col gap-2 p-5 sm:p-7 md:p-9">
+      <div class="flex items-center gap-4 text-xl font-semibold text-immich-primary dark:text-immich-dark-primary">
+        <span class="flex gap-2 items-center">
+          <svelte:component this={icon} size="1.25em" class="shrink-0 hidden sm:block" />
+          {title.toUpperCase()}
+        </span>
+        <div class="flex gap-2">
+          {#if jobCounts.failed > 0}
+            <Badge color="primary">
+              {jobCounts.failed.toLocaleString($locale)} failed
+            </Badge>
+          {/if}
+          {#if jobCounts.delayed > 0}
+            <Badge color="secondary">
+              {jobCounts.delayed.toLocaleString($locale)} delayed
+            </Badge>
+          {/if}
+        </div>
+      </div>
 
-			{#if subtitle}
-				<div class="text-sm dark:text-white whitespace-pre-line">{subtitle}</div>
-			{/if}
+      {#if subtitle}
+        <div class="text-sm dark:text-white whitespace-pre-line">{subtitle}</div>
+      {/if}
 
-			{#if slots?.description}
-				<div class="text-sm dark:text-white">
-					<slot name="description" />
-				</div>
-			{/if}
+      {#if slots?.description}
+        <div class="text-sm dark:text-white">
+          <slot name="description" />
+        </div>
+      {/if}
 
-			<div class="flex w-full max-w-md mt-2 flex-col sm:flex-row">
-				<div
-					class="{commonClasses} bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray rounded-t-lg sm:rounded-l-lg sm:rounded-r-none"
-				>
-					<p>Active</p>
-					<p class="text-2xl">
-						{jobCounts.active.toLocaleString($locale)}
-					</p>
-				</div>
+      <div class="flex w-full max-w-md mt-2 flex-col sm:flex-row">
+        <div
+          class="{commonClasses} bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray rounded-t-lg sm:rounded-l-lg sm:rounded-r-none"
+        >
+          <p>Active</p>
+          <p class="text-2xl">
+            {jobCounts.active.toLocaleString($locale)}
+          </p>
+        </div>
 
-				<div
-					class="{commonClasses} bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray rounded-b-lg sm:rounded-r-lg sm:rounded-l-none flex-row-reverse"
-				>
-					<p class="text-2xl">
-						{waitingCount.toLocaleString($locale)}
-					</p>
-					<p>Waiting</p>
-				</div>
-			</div>
-		</div>
-	</div>
-	<div class="flex sm:flex-col flex-row sm:w-32 w-full overflow-hidden">
-		{#if !isIdle}
-			{#if waitingCount > 0}
-				<JobTileButton
-					color="gray"
-					on:click={() => dispatch('command', { command: JobCommand.Empty, force: false })}
-				>
-					<Close size="24" /> CLEAR
-				</JobTileButton>
-			{/if}
-			{#if queueStatus.isPaused}
-				{@const size = waitingCount > 0 ? '24' : '48'}
-				<JobTileButton
-					color="light-gray"
-					on:click={() => dispatch('command', { command: JobCommand.Resume, force: false })}
-				>
-					<!-- size property is not reactive, so have to use width and height -->
-					<FastForward width={size} height={size} /> RESUME
-				</JobTileButton>
-			{:else}
-				<JobTileButton
-					color="light-gray"
-					on:click={() => dispatch('command', { command: JobCommand.Pause, force: false })}
-				>
-					<Pause size="24" /> PAUSE
-				</JobTileButton>
-			{/if}
-		{:else if allowForceCommand}
-			<JobTileButton
-				color="gray"
-				on:click={() => dispatch('command', { command: JobCommand.Start, force: true })}
-			>
-				<AllInclusive size="24" />
-				{allText}
-			</JobTileButton>
-			<JobTileButton
-				color="light-gray"
-				on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
-			>
-				<SelectionSearch size="24" />
-				{missingText}
-			</JobTileButton>
-		{:else}
-			<JobTileButton
-				color="light-gray"
-				on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
-			>
-				<Play size="48" /> START
-			</JobTileButton>
-		{/if}
-	</div>
+        <div
+          class="{commonClasses} bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray rounded-b-lg sm:rounded-r-lg sm:rounded-l-none flex-row-reverse"
+        >
+          <p class="text-2xl">
+            {waitingCount.toLocaleString($locale)}
+          </p>
+          <p>Waiting</p>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="flex sm:flex-col flex-row sm:w-32 w-full overflow-hidden">
+    {#if !isIdle}
+      {#if waitingCount > 0}
+        <JobTileButton color="gray" on:click={() => dispatch('command', { command: JobCommand.Empty, force: false })}>
+          <Close size="24" /> CLEAR
+        </JobTileButton>
+      {/if}
+      {#if queueStatus.isPaused}
+        {@const size = waitingCount > 0 ? '24' : '48'}
+        <JobTileButton
+          color="light-gray"
+          on:click={() => dispatch('command', { command: JobCommand.Resume, force: false })}
+        >
+          <!-- size property is not reactive, so have to use width and height -->
+          <FastForward width={size} height={size} /> RESUME
+        </JobTileButton>
+      {:else}
+        <JobTileButton
+          color="light-gray"
+          on:click={() => dispatch('command', { command: JobCommand.Pause, force: false })}
+        >
+          <Pause size="24" /> PAUSE
+        </JobTileButton>
+      {/if}
+    {:else if allowForceCommand}
+      <JobTileButton color="gray" on:click={() => dispatch('command', { command: JobCommand.Start, force: true })}>
+        <AllInclusive size="24" />
+        {allText}
+      </JobTileButton>
+      <JobTileButton
+        color="light-gray"
+        on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
+      >
+        <SelectionSearch size="24" />
+        {missingText}
+      </JobTileButton>
+    {:else}
+      <JobTileButton
+        color="light-gray"
+        on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
+      >
+        <Play size="48" /> START
+      </JobTileButton>
+    {/if}
+  </div>
 </div>
diff --git a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
index fee23f1736..55bc0cdf3c 100644
--- a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
+++ b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
@@ -1,160 +1,159 @@
 <script lang="ts">
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { AppRoute } from '$lib/constants';
-	import { handleError } from '$lib/utils/handle-error';
-	import { AllJobStatusResponseDto, api, JobCommand, JobCommandDto, JobName } from '@api';
-	import type { ComponentType } from 'svelte';
-	import type Icon from 'svelte-material-icons/DotsVertical.svelte';
-	import FaceRecognition from 'svelte-material-icons/FaceRecognition.svelte';
-	import FileJpgBox from 'svelte-material-icons/FileJpgBox.svelte';
-	import FileXmlBox from 'svelte-material-icons/FileXmlBox.svelte';
-	import FolderMove from 'svelte-material-icons/FolderMove.svelte';
-	import CogIcon from 'svelte-material-icons/Cog.svelte';
-	import Table from 'svelte-material-icons/Table.svelte';
-	import TagMultiple from 'svelte-material-icons/TagMultiple.svelte';
-	import VectorCircle from 'svelte-material-icons/VectorCircle.svelte';
-	import Video from 'svelte-material-icons/Video.svelte';
-	import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
-	import JobTile from './job-tile.svelte';
-	import StorageMigrationDescription from './storage-migration-description.svelte';
-	import Button from '../../elements/buttons/button.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { AppRoute } from '$lib/constants';
+  import { handleError } from '$lib/utils/handle-error';
+  import { AllJobStatusResponseDto, api, JobCommand, JobCommandDto, JobName } from '@api';
+  import type { ComponentType } from 'svelte';
+  import type Icon from 'svelte-material-icons/DotsVertical.svelte';
+  import FaceRecognition from 'svelte-material-icons/FaceRecognition.svelte';
+  import FileJpgBox from 'svelte-material-icons/FileJpgBox.svelte';
+  import FileXmlBox from 'svelte-material-icons/FileXmlBox.svelte';
+  import FolderMove from 'svelte-material-icons/FolderMove.svelte';
+  import CogIcon from 'svelte-material-icons/Cog.svelte';
+  import Table from 'svelte-material-icons/Table.svelte';
+  import TagMultiple from 'svelte-material-icons/TagMultiple.svelte';
+  import VectorCircle from 'svelte-material-icons/VectorCircle.svelte';
+  import Video from 'svelte-material-icons/Video.svelte';
+  import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
+  import JobTile from './job-tile.svelte';
+  import StorageMigrationDescription from './storage-migration-description.svelte';
+  import Button from '../../elements/buttons/button.svelte';
 
-	export let jobs: AllJobStatusResponseDto;
+  export let jobs: AllJobStatusResponseDto;
 
-	interface JobDetails {
-		title: string;
-		subtitle?: string;
-		allText?: string;
-		missingText?: string;
-		icon: typeof Icon;
-		allowForceCommand?: boolean;
-		component?: ComponentType;
-		handleCommand?: (jobId: JobName, jobCommand: JobCommandDto) => Promise<void>;
-	}
+  interface JobDetails {
+    title: string;
+    subtitle?: string;
+    allText?: string;
+    missingText?: string;
+    icon: typeof Icon;
+    allowForceCommand?: boolean;
+    component?: ComponentType;
+    handleCommand?: (jobId: JobName, jobCommand: JobCommandDto) => Promise<void>;
+  }
 
-	let faceConfirm = false;
+  let faceConfirm = false;
 
-	const handleFaceCommand = async (jobId: JobName, dto: JobCommandDto) => {
-		if (dto.force) {
-			faceConfirm = true;
-			return;
-		}
+  const handleFaceCommand = async (jobId: JobName, dto: JobCommandDto) => {
+    if (dto.force) {
+      faceConfirm = true;
+      return;
+    }
 
-		await handleCommand(jobId, dto);
-	};
+    await handleCommand(jobId, dto);
+  };
 
-	const onFaceConfirm = () => {
-		faceConfirm = false;
-		handleCommand(JobName.RecognizeFaces, { command: JobCommand.Start, force: true });
-	};
+  const onFaceConfirm = () => {
+    faceConfirm = false;
+    handleCommand(JobName.RecognizeFaces, { command: JobCommand.Start, force: true });
+  };
 
-	const jobDetails: Partial<Record<JobName, JobDetails>> = {
-		[JobName.ThumbnailGeneration]: {
-			icon: FileJpgBox,
-			title: api.getJobName(JobName.ThumbnailGeneration),
-			subtitle: 'Regenerate JPEG and WebP thumbnails'
-		},
-		[JobName.MetadataExtraction]: {
-			icon: Table,
-			title: api.getJobName(JobName.MetadataExtraction),
-			subtitle: 'Extract metadata information i.e. GPS, resolution...etc'
-		},
-		[JobName.Sidecar]: {
-			title: api.getJobName(JobName.Sidecar),
-			icon: FileXmlBox,
-			subtitle: 'Discover or synchronize sidecar metadata from the filesystem',
-			allText: 'SYNC',
-			missingText: 'DISCOVER'
-		},
-		[JobName.ObjectTagging]: {
-			icon: TagMultiple,
-			title: api.getJobName(JobName.ObjectTagging),
-			subtitle:
-				'Run machine learning to tag objects\nNote that some assets may not have any objects detected'
-		},
-		[JobName.ClipEncoding]: {
-			icon: VectorCircle,
-			title: api.getJobName(JobName.ClipEncoding),
-			subtitle: 'Run machine learning to generate clip embeddings'
-		},
-		[JobName.RecognizeFaces]: {
-			icon: FaceRecognition,
-			title: api.getJobName(JobName.RecognizeFaces),
-			subtitle: 'Run machine learning to recognize faces',
-			handleCommand: handleFaceCommand
-		},
-		[JobName.VideoConversion]: {
-			icon: Video,
-			title: api.getJobName(JobName.VideoConversion),
-			subtitle: 'Transcode videos not in the desired format'
-		},
-		[JobName.StorageTemplateMigration]: {
-			icon: FolderMove,
-			title: api.getJobName(JobName.StorageTemplateMigration),
-			allowForceCommand: false,
-			component: StorageMigrationDescription
-		}
-	};
+  const jobDetails: Partial<Record<JobName, JobDetails>> = {
+    [JobName.ThumbnailGeneration]: {
+      icon: FileJpgBox,
+      title: api.getJobName(JobName.ThumbnailGeneration),
+      subtitle: 'Regenerate JPEG and WebP thumbnails',
+    },
+    [JobName.MetadataExtraction]: {
+      icon: Table,
+      title: api.getJobName(JobName.MetadataExtraction),
+      subtitle: 'Extract metadata information i.e. GPS, resolution...etc',
+    },
+    [JobName.Sidecar]: {
+      title: api.getJobName(JobName.Sidecar),
+      icon: FileXmlBox,
+      subtitle: 'Discover or synchronize sidecar metadata from the filesystem',
+      allText: 'SYNC',
+      missingText: 'DISCOVER',
+    },
+    [JobName.ObjectTagging]: {
+      icon: TagMultiple,
+      title: api.getJobName(JobName.ObjectTagging),
+      subtitle: 'Run machine learning to tag objects\nNote that some assets may not have any objects detected',
+    },
+    [JobName.ClipEncoding]: {
+      icon: VectorCircle,
+      title: api.getJobName(JobName.ClipEncoding),
+      subtitle: 'Run machine learning to generate clip embeddings',
+    },
+    [JobName.RecognizeFaces]: {
+      icon: FaceRecognition,
+      title: api.getJobName(JobName.RecognizeFaces),
+      subtitle: 'Run machine learning to recognize faces',
+      handleCommand: handleFaceCommand,
+    },
+    [JobName.VideoConversion]: {
+      icon: Video,
+      title: api.getJobName(JobName.VideoConversion),
+      subtitle: 'Transcode videos not in the desired format',
+    },
+    [JobName.StorageTemplateMigration]: {
+      icon: FolderMove,
+      title: api.getJobName(JobName.StorageTemplateMigration),
+      allowForceCommand: false,
+      component: StorageMigrationDescription,
+    },
+  };
 
-	const jobDetailsArray = Object.entries(jobDetails) as [JobName, JobDetails][];
+  const jobDetailsArray = Object.entries(jobDetails) as [JobName, JobDetails][];
 
-	async function handleCommand(jobId: JobName, jobCommand: JobCommandDto) {
-		const title = jobDetails[jobId]?.title;
+  async function handleCommand(jobId: JobName, jobCommand: JobCommandDto) {
+    const title = jobDetails[jobId]?.title;
 
-		try {
-			const { data } = await api.jobApi.sendJobCommand({ id: jobId, jobCommandDto: jobCommand });
-			jobs[jobId] = data;
+    try {
+      const { data } = await api.jobApi.sendJobCommand({ id: jobId, jobCommandDto: jobCommand });
+      jobs[jobId] = data;
 
-			switch (jobCommand.command) {
-				case JobCommand.Empty:
-					notificationController.show({
-						message: `Cleared jobs for: ${title}`,
-						type: NotificationType.Info
-					});
-					break;
-			}
-		} catch (error) {
-			handleError(error, `Command '${jobCommand.command}' failed for job: ${title}`);
-		}
-	}
+      switch (jobCommand.command) {
+        case JobCommand.Empty:
+          notificationController.show({
+            message: `Cleared jobs for: ${title}`,
+            type: NotificationType.Info,
+          });
+          break;
+      }
+    } catch (error) {
+      handleError(error, `Command '${jobCommand.command}' failed for job: ${title}`);
+    }
+  }
 </script>
 
 {#if faceConfirm}
-	<ConfirmDialogue
-		prompt="Are you sure you want to reprocess all faces? This will also clear named people."
-		on:confirm={onFaceConfirm}
-		on:cancel={() => (faceConfirm = false)}
-	/>
+  <ConfirmDialogue
+    prompt="Are you sure you want to reprocess all faces? This will also clear named people."
+    on:confirm={onFaceConfirm}
+    on:cancel={() => (faceConfirm = false)}
+  />
 {/if}
 
 <div class="flex flex-col gap-7">
-	<div class="flex justify-end">
-		<a href="{AppRoute.ADMIN_SETTINGS}?open=job-settings">
-			<Button size="sm">
-				<CogIcon size="18" />
-				<span class="pl-2">Manage Concurrency</span>
-			</Button>
-		</a>
-	</div>
-	{#each jobDetailsArray as [jobName, { title, subtitle, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]}
-		{@const { jobCounts, queueStatus } = jobs[jobName]}
-		<JobTile
-			{icon}
-			{title}
-			{subtitle}
-			allText={allText || 'ALL'}
-			missingText={missingText || 'MISSING'}
-			{allowForceCommand}
-			{jobCounts}
-			{queueStatus}
-			on:command={({ detail }) => (handleCommandOverride || handleCommand)(jobName, detail)}
-		>
-			{#if component}
-				<svelte:component this={component} slot="description" />
-			{/if}
-		</JobTile>
-	{/each}
+  <div class="flex justify-end">
+    <a href="{AppRoute.ADMIN_SETTINGS}?open=job-settings">
+      <Button size="sm">
+        <CogIcon size="18" />
+        <span class="pl-2">Manage Concurrency</span>
+      </Button>
+    </a>
+  </div>
+  {#each jobDetailsArray as [jobName, { title, subtitle, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]}
+    {@const { jobCounts, queueStatus } = jobs[jobName]}
+    <JobTile
+      {icon}
+      {title}
+      {subtitle}
+      allText={allText || 'ALL'}
+      missingText={missingText || 'MISSING'}
+      {allowForceCommand}
+      {jobCounts}
+      {queueStatus}
+      on:command={({ detail }) => (handleCommandOverride || handleCommand)(jobName, detail)}
+    >
+      {#if component}
+        <svelte:component this={component} slot="description" />
+      {/if}
+    </JobTile>
+  {/each}
 </div>
diff --git a/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte b/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte
index d5cae7ccf3..d840065954 100644
--- a/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte
+++ b/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte
@@ -1,10 +1,9 @@
 <script lang="ts">
-	import { AppRoute } from '$lib/constants';
+  import { AppRoute } from '$lib/constants';
 </script>
 
 Apply the current
-<a
-	href={`${AppRoute.ADMIN_SETTINGS}?open=storage-template`}
-	class="text-immich-primary dark:text-immich-dark-primary">Storage template</a
+<a href={`${AppRoute.ADMIN_SETTINGS}?open=storage-template`} class="text-immich-primary dark:text-immich-dark-primary"
+  >Storage template</a
 >
 to previously uploaded assets
diff --git a/web/src/lib/components/admin-page/restore-dialoge.svelte b/web/src/lib/components/admin-page/restore-dialoge.svelte
index d780064135..7ee68daef3 100644
--- a/web/src/lib/components/admin-page/restore-dialoge.svelte
+++ b/web/src/lib/components/admin-page/restore-dialoge.svelte
@@ -1,27 +1,21 @@
 <script lang="ts">
-	import { api, UserResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import { api, UserResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	const restoreUser = async () => {
-		const restoredUser = await api.userApi.restoreUser({ userId: user.id });
-		if (restoredUser.data.deletedAt == null) dispatch('user-restore-success');
-		else dispatch('user-restore-fail');
-	};
+  const restoreUser = async () => {
+    const restoredUser = await api.userApi.restoreUser({ userId: user.id });
+    if (restoredUser.data.deletedAt == null) dispatch('user-restore-success');
+    else dispatch('user-restore-fail');
+  };
 </script>
 
-<ConfirmDialogue
-	title="Restore User"
-	confirmText="Continue"
-	confirmColor="green"
-	on:confirm={restoreUser}
-	on:cancel
->
-	<svelte:fragment slot="prompt">
-		<p><b>{user.firstName} {user.lastName}</b>'s account will be restored.</p>
-	</svelte:fragment>
+<ConfirmDialogue title="Restore User" confirmText="Continue" confirmColor="green" on:confirm={restoreUser} on:cancel>
+  <svelte:fragment slot="prompt">
+    <p><b>{user.firstName} {user.lastName}</b>'s account will be restored.</p>
+  </svelte:fragment>
 </ConfirmDialogue>
diff --git a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte
index c1d26f1151..9305f98d72 100644
--- a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte
+++ b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte
@@ -1,122 +1,109 @@
 <script lang="ts">
-	import { locale } from '$lib/stores/preferences.store';
-	import type { ServerStatsResponseDto } from '@api';
-	import CameraIris from 'svelte-material-icons/CameraIris.svelte';
-	import Memory from 'svelte-material-icons/Memory.svelte';
-	import PlayCircle from 'svelte-material-icons/PlayCircle.svelte';
-	import { asByteUnitString, getBytesWithUnit } from '../../../utils/byte-units';
-	import StatsCard from './stats-card.svelte';
+  import { locale } from '$lib/stores/preferences.store';
+  import type { ServerStatsResponseDto } from '@api';
+  import CameraIris from 'svelte-material-icons/CameraIris.svelte';
+  import Memory from 'svelte-material-icons/Memory.svelte';
+  import PlayCircle from 'svelte-material-icons/PlayCircle.svelte';
+  import { asByteUnitString, getBytesWithUnit } from '../../../utils/byte-units';
+  import StatsCard from './stats-card.svelte';
 
-	export let stats: ServerStatsResponseDto = {
-		photos: 0,
-		videos: 0,
-		usage: 0,
-		usageByUser: []
-	};
+  export let stats: ServerStatsResponseDto = {
+    photos: 0,
+    videos: 0,
+    usage: 0,
+    usageByUser: [],
+  };
 
-	$: zeros = (value: number) => {
-		const maxLength = 13;
-		const valueLength = value.toString().length;
-		const zeroLength = maxLength - valueLength;
+  $: zeros = (value: number) => {
+    const maxLength = 13;
+    const valueLength = value.toString().length;
+    const zeroLength = maxLength - valueLength;
 
-		return '0'.repeat(zeroLength);
-	};
+    return '0'.repeat(zeroLength);
+  };
 
-	$: [statsUsage, statsUsageUnit] = getBytesWithUnit(stats.usage, 0);
+  $: [statsUsage, statsUsageUnit] = getBytesWithUnit(stats.usage, 0);
 </script>
 
 <div class="flex flex-col gap-5">
-	<div>
-		<p class="text-sm dark:text-immich-dark-fg">TOTAL USAGE</p>
+  <div>
+    <p class="text-sm dark:text-immich-dark-fg">TOTAL USAGE</p>
 
-		<div class="mt-5 justify-between lg:flex hidden">
-			<StatsCard logo={CameraIris} title="PHOTOS" value={stats.photos} />
-			<StatsCard logo={PlayCircle} title="VIDEOS" value={stats.videos} />
-			<StatsCard logo={Memory} title="STORAGE" value={statsUsage} unit={statsUsageUnit} />
-		</div>
-		<div class="mt-5 lg:hidden flex">
-			<div
-				class="bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between"
-			>
-				<div class="flex flex-wrap gap-x-12">
-					<div
-						class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary"
-					>
-						<CameraIris size="25" />
-						<p>PHOTOS</p>
-					</div>
+    <div class="mt-5 justify-between lg:flex hidden">
+      <StatsCard logo={CameraIris} title="PHOTOS" value={stats.photos} />
+      <StatsCard logo={PlayCircle} title="VIDEOS" value={stats.videos} />
+      <StatsCard logo={Memory} title="STORAGE" value={statsUsage} unit={statsUsageUnit} />
+    </div>
+    <div class="mt-5 lg:hidden flex">
+      <div class="bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between">
+        <div class="flex flex-wrap gap-x-12">
+          <div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
+            <CameraIris size="25" />
+            <p>PHOTOS</p>
+          </div>
 
-					<div class="relative text-center font-mono font-semibold text-2xl">
-						<span class="text-[#DCDADA] dark:text-[#525252]">{zeros(stats.photos)}</span><span
-							class="text-immich-primary dark:text-immich-dark-primary">{stats.photos}</span
-						>
-					</div>
-				</div>
-				<div class="flex flex-wrap gap-x-12">
-					<div
-						class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary"
-					>
-						<PlayCircle size="25" />
-						<p>VIDEOS</p>
-					</div>
+          <div class="relative text-center font-mono font-semibold text-2xl">
+            <span class="text-[#DCDADA] dark:text-[#525252]">{zeros(stats.photos)}</span><span
+              class="text-immich-primary dark:text-immich-dark-primary">{stats.photos}</span
+            >
+          </div>
+        </div>
+        <div class="flex flex-wrap gap-x-12">
+          <div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
+            <PlayCircle size="25" />
+            <p>VIDEOS</p>
+          </div>
 
-					<div class="relative text-center font-mono font-semibold text-2xl">
-						<span class="text-[#DCDADA] dark:text-[#525252]">{zeros(stats.videos)}</span><span
-							class="text-immich-primary dark:text-immich-dark-primary">{stats.videos}</span
-						>
-					</div>
-				</div>
-				<div class="flex flex-wrap gap-x-7">
-					<div
-						class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary"
-					>
-						<Memory size="25" />
-						<p>STORAGE</p>
-					</div>
+          <div class="relative text-center font-mono font-semibold text-2xl">
+            <span class="text-[#DCDADA] dark:text-[#525252]">{zeros(stats.videos)}</span><span
+              class="text-immich-primary dark:text-immich-dark-primary">{stats.videos}</span
+            >
+          </div>
+        </div>
+        <div class="flex flex-wrap gap-x-7">
+          <div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
+            <Memory size="25" />
+            <p>STORAGE</p>
+          </div>
 
-					<div class="relative text-center font-mono font-semibold text-2xl flex">
-						<span class="text-[#DCDADA] dark:text-[#525252]">{zeros(statsUsage)}</span><span
-							class="text-immich-primary dark:text-immich-dark-primary">{statsUsage}</span
-						>
-						<span class="text-center my-auto ml-2 text-base font-light text-gray-400"
-							>{statsUsageUnit}</span
-						>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
+          <div class="relative text-center font-mono font-semibold text-2xl flex">
+            <span class="text-[#DCDADA] dark:text-[#525252]">{zeros(statsUsage)}</span><span
+              class="text-immich-primary dark:text-immich-dark-primary">{statsUsage}</span
+            >
+            <span class="text-center my-auto ml-2 text-base font-light text-gray-400">{statsUsageUnit}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 
-	<div>
-		<p class="text-sm dark:text-immich-dark-fg">USER USAGE DETAIL</p>
-		<table class="text-left w-full mt-5">
-			<thead
-				class="border rounded-md mb-4 bg-gray-50 dark:bg-immich-dark-gray dark:border-immich-dark-gray flex text-immich-primary dark:text-immich-dark-primary w-full h-12"
-			>
-				<tr class="flex w-full place-items-center">
-					<th class="text-center w-1/4 font-medium text-sm">User</th>
-					<th class="text-center w-1/4 font-medium text-sm">Photos</th>
-					<th class="text-center w-1/4 font-medium text-sm">Videos</th>
-					<th class="text-center w-1/4 font-medium text-sm">Size</th>
-				</tr>
-			</thead>
-			<tbody
-				class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray dark:text-immich-dark-fg"
-			>
-				{#each stats.usageByUser as user (user.userId)}
-					<tr
-						class="text-center flex place-items-center w-full h-[50px] even:bg-immich-bg even:dark:bg-immich-dark-gray/50 odd:bg-immich-gray odd:dark:bg-immich-dark-gray/75"
-					>
-						<td class="text-sm px-2 w-1/4 text-ellipsis"
-							>{user.userFirstName} {user.userLastName}</td
-						>
-						<td class="text-sm px-2 w-1/4 text-ellipsis">{user.photos.toLocaleString($locale)}</td>
-						<td class="text-sm px-2 w-1/4 text-ellipsis">{user.videos.toLocaleString($locale)}</td>
-						<td class="text-sm px-2 w-1/4 text-ellipsis">{asByteUnitString(user.usage, $locale)}</td
-						>
-					</tr>
-				{/each}
-			</tbody>
-		</table>
-	</div>
+  <div>
+    <p class="text-sm dark:text-immich-dark-fg">USER USAGE DETAIL</p>
+    <table class="text-left w-full mt-5">
+      <thead
+        class="border rounded-md mb-4 bg-gray-50 dark:bg-immich-dark-gray dark:border-immich-dark-gray flex text-immich-primary dark:text-immich-dark-primary w-full h-12"
+      >
+        <tr class="flex w-full place-items-center">
+          <th class="text-center w-1/4 font-medium text-sm">User</th>
+          <th class="text-center w-1/4 font-medium text-sm">Photos</th>
+          <th class="text-center w-1/4 font-medium text-sm">Videos</th>
+          <th class="text-center w-1/4 font-medium text-sm">Size</th>
+        </tr>
+      </thead>
+      <tbody
+        class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray dark:text-immich-dark-fg"
+      >
+        {#each stats.usageByUser as user (user.userId)}
+          <tr
+            class="text-center flex place-items-center w-full h-[50px] even:bg-immich-bg even:dark:bg-immich-dark-gray/50 odd:bg-immich-gray odd:dark:bg-immich-dark-gray/75"
+          >
+            <td class="text-sm px-2 w-1/4 text-ellipsis">{user.userFirstName} {user.userLastName}</td>
+            <td class="text-sm px-2 w-1/4 text-ellipsis">{user.photos.toLocaleString($locale)}</td>
+            <td class="text-sm px-2 w-1/4 text-ellipsis">{user.videos.toLocaleString($locale)}</td>
+            <td class="text-sm px-2 w-1/4 text-ellipsis">{asByteUnitString(user.usage, $locale)}</td>
+          </tr>
+        {/each}
+      </tbody>
+    </table>
+  </div>
 </div>
diff --git a/web/src/lib/components/admin-page/server-stats/stats-card.svelte b/web/src/lib/components/admin-page/server-stats/stats-card.svelte
index 4a704a787d..3579638ba6 100644
--- a/web/src/lib/components/admin-page/server-stats/stats-card.svelte
+++ b/web/src/lib/components/admin-page/server-stats/stats-card.svelte
@@ -1,34 +1,32 @@
 <script lang="ts">
-	import type Icon from 'svelte-material-icons/AbTesting.svelte';
+  import type Icon from 'svelte-material-icons/AbTesting.svelte';
 
-	export let logo: typeof Icon;
-	export let title: string;
-	export let value: number;
-	export let unit: string | undefined = undefined;
+  export let logo: typeof Icon;
+  export let title: string;
+  export let value: number;
+  export let unit: string | undefined = undefined;
 
-	$: zeros = () => {
-		const maxLength = 13;
-		const valueLength = value.toString().length;
-		const zeroLength = maxLength - valueLength;
+  $: zeros = () => {
+    const maxLength = 13;
+    const valueLength = value.toString().length;
+    const zeroLength = maxLength - valueLength;
 
-		return '0'.repeat(zeroLength);
-	};
+    return '0'.repeat(zeroLength);
+  };
 </script>
 
-<div
-	class="w-[250px] h-[140px] bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between"
->
-	<div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
-		<svelte:component this={logo} size="40" />
-		<p>{title}</p>
-	</div>
+<div class="w-[250px] h-[140px] bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between">
+  <div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
+    <svelte:component this={logo} size="40" />
+    <p>{title}</p>
+  </div>
 
-	<div class="relative text-center font-mono font-semibold text-2xl">
-		<span class="text-[#DCDADA] dark:text-[#525252]">{zeros()}</span><span
-			class="text-immich-primary dark:text-immich-dark-primary">{value}</span
-		>
-		{#if unit}
-			<span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span>
-		{/if}
-	</div>
+  <div class="relative text-center font-mono font-semibold text-2xl">
+    <span class="text-[#DCDADA] dark:text-[#525252]">{zeros()}</span><span
+      class="text-immich-primary dark:text-immich-dark-primary">{value}</span
+    >
+    {#if unit}
+      <span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span>
+    {/if}
+  </div>
 </div>
diff --git a/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte b/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte
index 37f740d430..b0c3e650d0 100644
--- a/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte
+++ b/web/src/lib/components/admin-page/settings/confirm-disable-login.svelte
@@ -1,22 +1,22 @@
 <script lang="ts">
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 </script>
 
 <ConfirmDialogue title="Disable Login" on:cancel on:confirm>
-	<svelte:fragment slot="prompt">
-		<div class="flex flex-col gap-4">
-			<p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
-			<p>
-				To re-enable, use a
-				<a
-					href="https://immich.app/docs/administration/server-commands"
-					rel="noreferrer"
-					target="_blank"
-					class="underline"
-				>
-					Server Command</a
-				>.
-			</p>
-		</div>
-	</svelte:fragment>
+  <svelte:fragment slot="prompt">
+    <div class="flex flex-col gap-4">
+      <p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
+      <p>
+        To re-enable, use a
+        <a
+          href="https://immich.app/docs/administration/server-commands"
+          rel="noreferrer"
+          target="_blank"
+          class="underline"
+        >
+          Server Command</a
+        >.
+      </p>
+    </div>
+  </svelte:fragment>
 </ConfirmDialogue>
diff --git a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte
index c78e6f72d6..0b3d3b9818 100644
--- a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte
@@ -1,211 +1,211 @@
 <script lang="ts">
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { api, SystemConfigFFmpegDto, SystemConfigFFmpegDtoTranscodeEnum } from '@api';
-	import SettingButtonsRow from '../setting-buttons-row.svelte';
-	import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
-	import SettingSelect from '../setting-select.svelte';
-	import SettingSwitch from '../setting-switch.svelte';
-	import { isEqual } from 'lodash-es';
-	import { fade } from 'svelte/transition';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { api, SystemConfigFFmpegDto, SystemConfigFFmpegDtoTranscodeEnum } from '@api';
+  import SettingButtonsRow from '../setting-buttons-row.svelte';
+  import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
+  import SettingSelect from '../setting-select.svelte';
+  import SettingSwitch from '../setting-switch.svelte';
+  import { isEqual } from 'lodash-es';
+  import { fade } from 'svelte/transition';
 
-	export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
+  export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
 
-	let savedConfig: SystemConfigFFmpegDto;
-	let defaultConfig: SystemConfigFFmpegDto;
+  let savedConfig: SystemConfigFFmpegDto;
+  let defaultConfig: SystemConfigFFmpegDto;
 
-	async function getConfigs() {
-		[savedConfig, defaultConfig] = await Promise.all([
-			api.systemConfigApi.getConfig().then((res) => res.data.ffmpeg),
-			api.systemConfigApi.getDefaults().then((res) => res.data.ffmpeg)
-		]);
-	}
+  async function getConfigs() {
+    [savedConfig, defaultConfig] = await Promise.all([
+      api.systemConfigApi.getConfig().then((res) => res.data.ffmpeg),
+      api.systemConfigApi.getDefaults().then((res) => res.data.ffmpeg),
+    ]);
+  }
 
-	async function saveSetting() {
-		try {
-			const { data: configs } = await api.systemConfigApi.getConfig();
+  async function saveSetting() {
+    try {
+      const { data: configs } = await api.systemConfigApi.getConfig();
 
-			const result = await api.systemConfigApi.updateConfig({
-				systemConfigDto: {
-					...configs,
-					ffmpeg: ffmpegConfig
-				}
-			});
+      const result = await api.systemConfigApi.updateConfig({
+        systemConfigDto: {
+          ...configs,
+          ffmpeg: ffmpegConfig,
+        },
+      });
 
-			ffmpegConfig = { ...result.data.ffmpeg };
-			savedConfig = { ...result.data.ffmpeg };
+      ffmpegConfig = { ...result.data.ffmpeg };
+      savedConfig = { ...result.data.ffmpeg };
 
-			notificationController.show({
-				message: 'FFmpeg settings saved',
-				type: NotificationType.Info
-			});
-		} catch (e) {
-			console.error('Error [ffmpeg-settings] [saveSetting]', e);
-			notificationController.show({
-				message: 'Unable to save settings',
-				type: NotificationType.Error
-			});
-		}
-	}
+      notificationController.show({
+        message: 'FFmpeg settings saved',
+        type: NotificationType.Info,
+      });
+    } catch (e) {
+      console.error('Error [ffmpeg-settings] [saveSetting]', e);
+      notificationController.show({
+        message: 'Unable to save settings',
+        type: NotificationType.Error,
+      });
+    }
+  }
 
-	async function reset() {
-		const { data: resetConfig } = await api.systemConfigApi.getConfig();
+  async function reset() {
+    const { data: resetConfig } = await api.systemConfigApi.getConfig();
 
-		ffmpegConfig = { ...resetConfig.ffmpeg };
-		savedConfig = { ...resetConfig.ffmpeg };
+    ffmpegConfig = { ...resetConfig.ffmpeg };
+    savedConfig = { ...resetConfig.ffmpeg };
 
-		notificationController.show({
-			message: 'Reset FFmpeg settings to the recent saved settings',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset FFmpeg settings to the recent saved settings',
+      type: NotificationType.Info,
+    });
+  }
 
-	async function resetToDefault() {
-		const { data: configs } = await api.systemConfigApi.getDefaults();
+  async function resetToDefault() {
+    const { data: configs } = await api.systemConfigApi.getDefaults();
 
-		ffmpegConfig = { ...configs.ffmpeg };
-		defaultConfig = { ...configs.ffmpeg };
+    ffmpegConfig = { ...configs.ffmpeg };
+    defaultConfig = { ...configs.ffmpeg };
 
-		notificationController.show({
-			message: 'Reset FFmpeg settings to default',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset FFmpeg settings to default',
+      type: NotificationType.Info,
+    });
+  }
 </script>
 
 <div>
-	{#await getConfigs() then}
-		<div in:fade={{ duration: 500 }}>
-			<form autocomplete="off" on:submit|preventDefault>
-				<div class="flex flex-col gap-4 ml-4 mt-4">
-					<SettingInputField
-						inputType={SettingInputFieldType.NUMBER}
-						label="CONSTANT RATE FACTOR (-crf)"
-						desc="Video quality level. Typical values are 23 for H.264, 28 for HEVC, and 31 for VP9. Lower is better, but takes longer to encode and produces larger files."
-						bind:value={ffmpegConfig.crf}
-						required={true}
-						isEdited={!(ffmpegConfig.crf == savedConfig.crf)}
-					/>
+  {#await getConfigs() then}
+    <div in:fade={{ duration: 500 }}>
+      <form autocomplete="off" on:submit|preventDefault>
+        <div class="flex flex-col gap-4 ml-4 mt-4">
+          <SettingInputField
+            inputType={SettingInputFieldType.NUMBER}
+            label="CONSTANT RATE FACTOR (-crf)"
+            desc="Video quality level. Typical values are 23 for H.264, 28 for HEVC, and 31 for VP9. Lower is better, but takes longer to encode and produces larger files."
+            bind:value={ffmpegConfig.crf}
+            required={true}
+            isEdited={!(ffmpegConfig.crf == savedConfig.crf)}
+          />
 
-					<SettingSelect
-						label="PRESET (-preset)"
-						desc="Compression speed. Slower presets produce smaller files, and increase quality when targeting a certain bitrate. VP9 ignores speeds above `faster`."
-						bind:value={ffmpegConfig.preset}
-						name="preset"
-						options={[
-							{ value: 'ultrafast', text: 'ultrafast' },
-							{ value: 'superfast', text: 'superfast' },
-							{ value: 'veryfast', text: 'veryfast' },
-							{ value: 'faster', text: 'faster' },
-							{ value: 'fast', text: 'fast' },
-							{ value: 'medium', text: 'medium' },
-							{ value: 'slow', text: 'slow' },
-							{ value: 'slower', text: 'slower' },
-							{ value: 'veryslow', text: 'veryslow' }
-						]}
-						isEdited={!(ffmpegConfig.preset == savedConfig.preset)}
-					/>
+          <SettingSelect
+            label="PRESET (-preset)"
+            desc="Compression speed. Slower presets produce smaller files, and increase quality when targeting a certain bitrate. VP9 ignores speeds above `faster`."
+            bind:value={ffmpegConfig.preset}
+            name="preset"
+            options={[
+              { value: 'ultrafast', text: 'ultrafast' },
+              { value: 'superfast', text: 'superfast' },
+              { value: 'veryfast', text: 'veryfast' },
+              { value: 'faster', text: 'faster' },
+              { value: 'fast', text: 'fast' },
+              { value: 'medium', text: 'medium' },
+              { value: 'slow', text: 'slow' },
+              { value: 'slower', text: 'slower' },
+              { value: 'veryslow', text: 'veryslow' },
+            ]}
+            isEdited={!(ffmpegConfig.preset == savedConfig.preset)}
+          />
 
-					<SettingSelect
-						label="AUDIO CODEC"
-						desc="Opus is the highest quality option, but has lower compatibility with old devices or software."
-						bind:value={ffmpegConfig.targetAudioCodec}
-						options={[
-							{ value: 'aac', text: 'aac' },
-							{ value: 'mp3', text: 'mp3' },
-							{ value: 'opus', text: 'opus' }
-						]}
-						name="acodec"
-						isEdited={!(ffmpegConfig.targetAudioCodec == savedConfig.targetAudioCodec)}
-					/>
+          <SettingSelect
+            label="AUDIO CODEC"
+            desc="Opus is the highest quality option, but has lower compatibility with old devices or software."
+            bind:value={ffmpegConfig.targetAudioCodec}
+            options={[
+              { value: 'aac', text: 'aac' },
+              { value: 'mp3', text: 'mp3' },
+              { value: 'opus', text: 'opus' },
+            ]}
+            name="acodec"
+            isEdited={!(ffmpegConfig.targetAudioCodec == savedConfig.targetAudioCodec)}
+          />
 
-					<SettingSelect
-						label="VIDEO CODEC"
-						desc="VP9 has high efficiency and web compatibility, but takes longer to transcode. HEVC performs similarly, but has lower web compatibility. H.264 is widely compatible and quick to transcode, but produces much larger files."
-						bind:value={ffmpegConfig.targetVideoCodec}
-						options={[
-							{ value: 'h264', text: 'h264' },
-							{ value: 'hevc', text: 'hevc' },
-							{ value: 'vp9', text: 'vp9' }
-						]}
-						name="vcodec"
-						isEdited={!(ffmpegConfig.targetVideoCodec == savedConfig.targetVideoCodec)}
-					/>
+          <SettingSelect
+            label="VIDEO CODEC"
+            desc="VP9 has high efficiency and web compatibility, but takes longer to transcode. HEVC performs similarly, but has lower web compatibility. H.264 is widely compatible and quick to transcode, but produces much larger files."
+            bind:value={ffmpegConfig.targetVideoCodec}
+            options={[
+              { value: 'h264', text: 'h264' },
+              { value: 'hevc', text: 'hevc' },
+              { value: 'vp9', text: 'vp9' },
+            ]}
+            name="vcodec"
+            isEdited={!(ffmpegConfig.targetVideoCodec == savedConfig.targetVideoCodec)}
+          />
 
-					<SettingSelect
-						label="TARGET RESOLUTION"
-						desc="Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
-						bind:value={ffmpegConfig.targetResolution}
-						options={[
-							{ value: '2160', text: '4k' },
-							{ value: '1440', text: '1440p' },
-							{ value: '1080', text: '1080p' },
-							{ value: '720', text: '720p' },
-							{ value: '480', text: '480p' },
-							{ value: 'original', text: 'original' }
-						]}
-						name="resolution"
-						isEdited={!(ffmpegConfig.targetResolution == savedConfig.targetResolution)}
-					/>
+          <SettingSelect
+            label="TARGET RESOLUTION"
+            desc="Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
+            bind:value={ffmpegConfig.targetResolution}
+            options={[
+              { value: '2160', text: '4k' },
+              { value: '1440', text: '1440p' },
+              { value: '1080', text: '1080p' },
+              { value: '720', text: '720p' },
+              { value: '480', text: '480p' },
+              { value: 'original', text: 'original' },
+            ]}
+            name="resolution"
+            isEdited={!(ffmpegConfig.targetResolution == savedConfig.targetResolution)}
+          />
 
-					<SettingInputField
-						inputType={SettingInputFieldType.TEXT}
-						label="MAX BITRATE"
-						desc="Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600k for VP9 or HEVC, or 4500k for H.264. Disabled if set to 0."
-						bind:value={ffmpegConfig.maxBitrate}
-						isEdited={!(ffmpegConfig.maxBitrate == savedConfig.maxBitrate)}
-					/>
+          <SettingInputField
+            inputType={SettingInputFieldType.TEXT}
+            label="MAX BITRATE"
+            desc="Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600k for VP9 or HEVC, or 4500k for H.264. Disabled if set to 0."
+            bind:value={ffmpegConfig.maxBitrate}
+            isEdited={!(ffmpegConfig.maxBitrate == savedConfig.maxBitrate)}
+          />
 
-					<SettingInputField
-						inputType={SettingInputFieldType.NUMBER}
-						label="THREADS"
-						desc="Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0."
-						bind:value={ffmpegConfig.threads}
-						isEdited={!(ffmpegConfig.threads == savedConfig.threads)}
-					/>
+          <SettingInputField
+            inputType={SettingInputFieldType.NUMBER}
+            label="THREADS"
+            desc="Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0."
+            bind:value={ffmpegConfig.threads}
+            isEdited={!(ffmpegConfig.threads == savedConfig.threads)}
+          />
 
-					<SettingSelect
-						label="TRANSCODE"
-						desc="Policy for when a video should be transcoded."
-						bind:value={ffmpegConfig.transcode}
-						name="transcode"
-						options={[
-							{ value: SystemConfigFFmpegDtoTranscodeEnum.All, text: 'All videos' },
-							{
-								value: SystemConfigFFmpegDtoTranscodeEnum.Optimal,
-								text: 'Videos higher than target resolution or not in the desired format'
-							},
-							{
-								value: SystemConfigFFmpegDtoTranscodeEnum.Required,
-								text: 'Only videos not in the desired format'
-							},
-							{
-								value: SystemConfigFFmpegDtoTranscodeEnum.Disabled,
-								text: "Don't transcode any videos, may break playback on some clients"
-							}
-						]}
-						isEdited={!(ffmpegConfig.transcode == savedConfig.transcode)}
-					/>
+          <SettingSelect
+            label="TRANSCODE"
+            desc="Policy for when a video should be transcoded."
+            bind:value={ffmpegConfig.transcode}
+            name="transcode"
+            options={[
+              { value: SystemConfigFFmpegDtoTranscodeEnum.All, text: 'All videos' },
+              {
+                value: SystemConfigFFmpegDtoTranscodeEnum.Optimal,
+                text: 'Videos higher than target resolution or not in the desired format',
+              },
+              {
+                value: SystemConfigFFmpegDtoTranscodeEnum.Required,
+                text: 'Only videos not in the desired format',
+              },
+              {
+                value: SystemConfigFFmpegDtoTranscodeEnum.Disabled,
+                text: "Don't transcode any videos, may break playback on some clients",
+              },
+            ]}
+            isEdited={!(ffmpegConfig.transcode == savedConfig.transcode)}
+          />
 
-					<SettingSwitch
-						title="TWO-PASS ENCODING"
-						subtitle="Transcode in two passes to produce better encoded videos. When max bitrate is enabled (required for it to work with H.264 and HEVC), this mode uses a bitrate range based on the max bitrate and ignores CRF. For VP9, CRF can be used if max bitrate is disabled."
-						bind:checked={ffmpegConfig.twoPass}
-						isEdited={!(ffmpegConfig.twoPass === savedConfig.twoPass)}
-					/>
-				</div>
+          <SettingSwitch
+            title="TWO-PASS ENCODING"
+            subtitle="Transcode in two passes to produce better encoded videos. When max bitrate is enabled (required for it to work with H.264 and HEVC), this mode uses a bitrate range based on the max bitrate and ignores CRF. For VP9, CRF can be used if max bitrate is disabled."
+            bind:checked={ffmpegConfig.twoPass}
+            isEdited={!(ffmpegConfig.twoPass === savedConfig.twoPass)}
+          />
+        </div>
 
-				<div class="ml-4">
-					<SettingButtonsRow
-						on:reset={reset}
-						on:save={saveSetting}
-						on:reset-to-default={resetToDefault}
-						showResetToDefault={!isEqual(savedConfig, defaultConfig)}
-					/>
-				</div>
-			</form>
-		</div>
-	{/await}
+        <div class="ml-4">
+          <SettingButtonsRow
+            on:reset={reset}
+            on:save={saveSetting}
+            on:reset-to-default={resetToDefault}
+            showResetToDefault={!isEqual(savedConfig, defaultConfig)}
+          />
+        </div>
+      </form>
+    </div>
+  {/await}
 </div>
diff --git a/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte b/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte
index 2c98af2960..37fc0a6199 100644
--- a/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte
@@ -1,103 +1,101 @@
 <script lang="ts">
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { api, JobName, SystemConfigJobDto } from '@api';
-	import { isEqual } from 'lodash-es';
-	import { fade } from 'svelte/transition';
-	import { handleError } from '../../../../utils/handle-error';
-	import SettingButtonsRow from '../setting-buttons-row.svelte';
-	import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { api, JobName, SystemConfigJobDto } from '@api';
+  import { isEqual } from 'lodash-es';
+  import { fade } from 'svelte/transition';
+  import { handleError } from '../../../../utils/handle-error';
+  import SettingButtonsRow from '../setting-buttons-row.svelte';
+  import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
 
-	export let jobConfig: SystemConfigJobDto; // this is the config that is being edited
+  export let jobConfig: SystemConfigJobDto; // this is the config that is being edited
 
-	let savedConfig: SystemConfigJobDto;
-	let defaultConfig: SystemConfigJobDto;
+  let savedConfig: SystemConfigJobDto;
+  let defaultConfig: SystemConfigJobDto;
 
-	const ignoredJobs = [JobName.BackgroundTask, JobName.Search] as JobName[];
-	const jobNames = Object.values(JobName).filter(
-		(jobName) => !ignoredJobs.includes(jobName as JobName)
-	);
+  const ignoredJobs = [JobName.BackgroundTask, JobName.Search] as JobName[];
+  const jobNames = Object.values(JobName).filter((jobName) => !ignoredJobs.includes(jobName as JobName));
 
-	async function getConfigs() {
-		[savedConfig, defaultConfig] = await Promise.all([
-			api.systemConfigApi.getConfig().then((res) => res.data.job),
-			api.systemConfigApi.getDefaults().then((res) => res.data.job)
-		]);
-	}
+  async function getConfigs() {
+    [savedConfig, defaultConfig] = await Promise.all([
+      api.systemConfigApi.getConfig().then((res) => res.data.job),
+      api.systemConfigApi.getDefaults().then((res) => res.data.job),
+    ]);
+  }
 
-	async function saveSetting() {
-		try {
-			const { data: configs } = await api.systemConfigApi.getConfig();
+  async function saveSetting() {
+    try {
+      const { data: configs } = await api.systemConfigApi.getConfig();
 
-			const result = await api.systemConfigApi.updateConfig({
-				systemConfigDto: {
-					...configs,
-					job: jobConfig
-				}
-			});
+      const result = await api.systemConfigApi.updateConfig({
+        systemConfigDto: {
+          ...configs,
+          job: jobConfig,
+        },
+      });
 
-			jobConfig = { ...result.data.job };
-			savedConfig = { ...result.data.job };
+      jobConfig = { ...result.data.job };
+      savedConfig = { ...result.data.job };
 
-			notificationController.show({ message: 'Job settings saved', type: NotificationType.Info });
-		} catch (error) {
-			handleError(error, 'Unable to save settings');
-		}
-	}
+      notificationController.show({ message: 'Job settings saved', type: NotificationType.Info });
+    } catch (error) {
+      handleError(error, 'Unable to save settings');
+    }
+  }
 
-	async function reset() {
-		const { data: resetConfig } = await api.systemConfigApi.getConfig();
+  async function reset() {
+    const { data: resetConfig } = await api.systemConfigApi.getConfig();
 
-		jobConfig = { ...resetConfig.job };
-		savedConfig = { ...resetConfig.job };
+    jobConfig = { ...resetConfig.job };
+    savedConfig = { ...resetConfig.job };
 
-		notificationController.show({
-			message: 'Reset Job settings to the recent saved settings',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset Job settings to the recent saved settings',
+      type: NotificationType.Info,
+    });
+  }
 
-	async function resetToDefault() {
-		const { data: configs } = await api.systemConfigApi.getDefaults();
+  async function resetToDefault() {
+    const { data: configs } = await api.systemConfigApi.getDefaults();
 
-		jobConfig = { ...configs.job };
-		defaultConfig = { ...configs.job };
+    jobConfig = { ...configs.job };
+    defaultConfig = { ...configs.job };
 
-		notificationController.show({
-			message: 'Reset Job settings to default',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset Job settings to default',
+      type: NotificationType.Info,
+    });
+  }
 </script>
 
 <div>
-	{#await getConfigs() then}
-		<div in:fade={{ duration: 500 }}>
-			<form autocomplete="off" on:submit|preventDefault>
-				{#each jobNames as jobName}
-					<div class="flex flex-col gap-4 ml-4 mt-4">
-						<SettingInputField
-							inputType={SettingInputFieldType.NUMBER}
-							label="{api.getJobName(jobName)} Concurrency"
-							desc=""
-							bind:value={jobConfig[jobName].concurrency}
-							required={true}
-							isEdited={!(jobConfig[jobName].concurrency == savedConfig[jobName].concurrency)}
-						/>
-					</div>
-				{/each}
+  {#await getConfigs() then}
+    <div in:fade={{ duration: 500 }}>
+      <form autocomplete="off" on:submit|preventDefault>
+        {#each jobNames as jobName}
+          <div class="flex flex-col gap-4 ml-4 mt-4">
+            <SettingInputField
+              inputType={SettingInputFieldType.NUMBER}
+              label="{api.getJobName(jobName)} Concurrency"
+              desc=""
+              bind:value={jobConfig[jobName].concurrency}
+              required={true}
+              isEdited={!(jobConfig[jobName].concurrency == savedConfig[jobName].concurrency)}
+            />
+          </div>
+        {/each}
 
-				<div class="ml-4">
-					<SettingButtonsRow
-						on:reset={reset}
-						on:save={saveSetting}
-						on:reset-to-default={resetToDefault}
-						showResetToDefault={!isEqual(savedConfig, defaultConfig)}
-					/>
-				</div>
-			</form>
-		</div>
-	{/await}
+        <div class="ml-4">
+          <SettingButtonsRow
+            on:reset={reset}
+            on:save={saveSetting}
+            on:reset-to-default={resetToDefault}
+            showResetToDefault={!isEqual(savedConfig, defaultConfig)}
+          />
+        </div>
+      </form>
+    </div>
+  {/await}
 </div>
diff --git a/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte b/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte
index 644135894e..ffee598f89 100644
--- a/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte
@@ -1,212 +1,209 @@
 <script lang="ts">
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { handleError } from '$lib/utils/handle-error';
-	import { api, SystemConfigOAuthDto } from '@api';
-	import { isEqual } from 'lodash-es';
-	import { fade } from 'svelte/transition';
-	import ConfirmDisableLogin from '../confirm-disable-login.svelte';
-	import SettingButtonsRow from '../setting-buttons-row.svelte';
-	import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
-	import SettingSwitch from '../setting-switch.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { handleError } from '$lib/utils/handle-error';
+  import { api, SystemConfigOAuthDto } from '@api';
+  import { isEqual } from 'lodash-es';
+  import { fade } from 'svelte/transition';
+  import ConfirmDisableLogin from '../confirm-disable-login.svelte';
+  import SettingButtonsRow from '../setting-buttons-row.svelte';
+  import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
+  import SettingSwitch from '../setting-switch.svelte';
 
-	export let oauthConfig: SystemConfigOAuthDto;
+  export let oauthConfig: SystemConfigOAuthDto;
 
-	let savedConfig: SystemConfigOAuthDto;
-	let defaultConfig: SystemConfigOAuthDto;
+  let savedConfig: SystemConfigOAuthDto;
+  let defaultConfig: SystemConfigOAuthDto;
 
-	const handleToggleOverride = () => {
-		// click runs before bind
-		const previouslyEnabled = oauthConfig.mobileOverrideEnabled;
-		if (!previouslyEnabled && !oauthConfig.mobileRedirectUri) {
-			oauthConfig.mobileRedirectUri = window.location.origin + '/api/oauth/mobile-redirect';
-		}
-	};
+  const handleToggleOverride = () => {
+    // click runs before bind
+    const previouslyEnabled = oauthConfig.mobileOverrideEnabled;
+    if (!previouslyEnabled && !oauthConfig.mobileRedirectUri) {
+      oauthConfig.mobileRedirectUri = window.location.origin + '/api/oauth/mobile-redirect';
+    }
+  };
 
-	async function getConfigs() {
-		[savedConfig, defaultConfig] = await Promise.all([
-			api.systemConfigApi.getConfig().then((res) => res.data.oauth),
-			api.systemConfigApi.getDefaults().then((res) => res.data.oauth)
-		]);
-	}
+  async function getConfigs() {
+    [savedConfig, defaultConfig] = await Promise.all([
+      api.systemConfigApi.getConfig().then((res) => res.data.oauth),
+      api.systemConfigApi.getDefaults().then((res) => res.data.oauth),
+    ]);
+  }
 
-	async function reset() {
-		const { data: resetConfig } = await api.systemConfigApi.getConfig();
+  async function reset() {
+    const { data: resetConfig } = await api.systemConfigApi.getConfig();
 
-		oauthConfig = { ...resetConfig.oauth };
-		savedConfig = { ...resetConfig.oauth };
+    oauthConfig = { ...resetConfig.oauth };
+    savedConfig = { ...resetConfig.oauth };
 
-		notificationController.show({
-			message: 'Reset OAuth settings to the last saved settings',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset OAuth settings to the last saved settings',
+      type: NotificationType.Info,
+    });
+  }
 
-	let isConfirmOpen = false;
-	let handleConfirm: (value: boolean) => void;
+  let isConfirmOpen = false;
+  let handleConfirm: (value: boolean) => void;
 
-	const openConfirmModal = () => {
-		return new Promise((resolve) => {
-			handleConfirm = (value: boolean) => {
-				isConfirmOpen = false;
-				resolve(value);
-			};
-			isConfirmOpen = true;
-		});
-	};
+  const openConfirmModal = () => {
+    return new Promise((resolve) => {
+      handleConfirm = (value: boolean) => {
+        isConfirmOpen = false;
+        resolve(value);
+      };
+      isConfirmOpen = true;
+    });
+  };
 
-	async function saveSetting() {
-		try {
-			const { data: current } = await api.systemConfigApi.getConfig();
+  async function saveSetting() {
+    try {
+      const { data: current } = await api.systemConfigApi.getConfig();
 
-			if (!current.passwordLogin.enabled && current.oauth.enabled && !oauthConfig.enabled) {
-				const confirmed = await openConfirmModal();
-				if (!confirmed) {
-					return;
-				}
-			}
+      if (!current.passwordLogin.enabled && current.oauth.enabled && !oauthConfig.enabled) {
+        const confirmed = await openConfirmModal();
+        if (!confirmed) {
+          return;
+        }
+      }
 
-			if (!oauthConfig.mobileOverrideEnabled) {
-				oauthConfig.mobileRedirectUri = '';
-			}
+      if (!oauthConfig.mobileOverrideEnabled) {
+        oauthConfig.mobileRedirectUri = '';
+      }
 
-			const { data: updated } = await api.systemConfigApi.updateConfig({
-				systemConfigDto: {
-					...current,
-					oauth: oauthConfig
-				}
-			});
+      const { data: updated } = await api.systemConfigApi.updateConfig({
+        systemConfigDto: {
+          ...current,
+          oauth: oauthConfig,
+        },
+      });
 
-			oauthConfig = { ...updated.oauth };
-			savedConfig = { ...updated.oauth };
+      oauthConfig = { ...updated.oauth };
+      savedConfig = { ...updated.oauth };
 
-			notificationController.show({ message: 'OAuth settings saved', type: NotificationType.Info });
-		} catch (error) {
-			handleError(error, 'Unable to save OAuth settings');
-		}
-	}
+      notificationController.show({ message: 'OAuth settings saved', type: NotificationType.Info });
+    } catch (error) {
+      handleError(error, 'Unable to save OAuth settings');
+    }
+  }
 
-	async function resetToDefault() {
-		const { data: defaultConfig } = await api.systemConfigApi.getDefaults();
+  async function resetToDefault() {
+    const { data: defaultConfig } = await api.systemConfigApi.getDefaults();
 
-		oauthConfig = { ...defaultConfig.oauth };
+    oauthConfig = { ...defaultConfig.oauth };
 
-		notificationController.show({
-			message: 'Reset OAuth settings to default',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset OAuth settings to default',
+      type: NotificationType.Info,
+    });
+  }
 </script>
 
 {#if isConfirmOpen}
-	<ConfirmDisableLogin
-		on:cancel={() => handleConfirm(false)}
-		on:confirm={() => handleConfirm(true)}
-	/>
+  <ConfirmDisableLogin on:cancel={() => handleConfirm(false)} on:confirm={() => handleConfirm(true)} />
 {/if}
 
 <div class="mt-2">
-	{#await getConfigs() then}
-		<div in:fade={{ duration: 500 }}>
-			<form autocomplete="off" on:submit|preventDefault class="flex flex-col mx-4 gap-4 py-4">
-				<p class="text-sm dark:text-immich-dark-fg">
-					For more details about this feature, refer to the <a
-						href="http://immich.app/docs/administration/oauth#mobile-redirect-uri"
-						class="underline"
-						target="_blank"
-						rel="noreferrer">docs</a
-					>.
-				</p>
+  {#await getConfigs() then}
+    <div in:fade={{ duration: 500 }}>
+      <form autocomplete="off" on:submit|preventDefault class="flex flex-col mx-4 gap-4 py-4">
+        <p class="text-sm dark:text-immich-dark-fg">
+          For more details about this feature, refer to the <a
+            href="http://immich.app/docs/administration/oauth#mobile-redirect-uri"
+            class="underline"
+            target="_blank"
+            rel="noreferrer">docs</a
+          >.
+        </p>
 
-				<SettingSwitch title="ENABLE" bind:checked={oauthConfig.enabled} />
-				<hr />
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="ISSUER URL"
-					bind:value={oauthConfig.issuerUrl}
-					required={true}
-					disabled={!oauthConfig.enabled}
-					isEdited={!(oauthConfig.issuerUrl == savedConfig.issuerUrl)}
-				/>
+        <SettingSwitch title="ENABLE" bind:checked={oauthConfig.enabled} />
+        <hr />
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="ISSUER URL"
+          bind:value={oauthConfig.issuerUrl}
+          required={true}
+          disabled={!oauthConfig.enabled}
+          isEdited={!(oauthConfig.issuerUrl == savedConfig.issuerUrl)}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="CLIENT ID"
-					bind:value={oauthConfig.clientId}
-					required={true}
-					disabled={!oauthConfig.enabled}
-					isEdited={!(oauthConfig.clientId == savedConfig.clientId)}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="CLIENT ID"
+          bind:value={oauthConfig.clientId}
+          required={true}
+          disabled={!oauthConfig.enabled}
+          isEdited={!(oauthConfig.clientId == savedConfig.clientId)}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="CLIENT SECRET"
-					bind:value={oauthConfig.clientSecret}
-					required={true}
-					disabled={!oauthConfig.enabled}
-					isEdited={!(oauthConfig.clientSecret == savedConfig.clientSecret)}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="CLIENT SECRET"
+          bind:value={oauthConfig.clientSecret}
+          required={true}
+          disabled={!oauthConfig.enabled}
+          isEdited={!(oauthConfig.clientSecret == savedConfig.clientSecret)}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="SCOPE"
-					bind:value={oauthConfig.scope}
-					required={true}
-					disabled={!oauthConfig.enabled}
-					isEdited={!(oauthConfig.scope == savedConfig.scope)}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="SCOPE"
+          bind:value={oauthConfig.scope}
+          required={true}
+          disabled={!oauthConfig.enabled}
+          isEdited={!(oauthConfig.scope == savedConfig.scope)}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="BUTTON TEXT"
-					bind:value={oauthConfig.buttonText}
-					required={false}
-					disabled={!oauthConfig.enabled}
-					isEdited={!(oauthConfig.buttonText == savedConfig.buttonText)}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="BUTTON TEXT"
+          bind:value={oauthConfig.buttonText}
+          required={false}
+          disabled={!oauthConfig.enabled}
+          isEdited={!(oauthConfig.buttonText == savedConfig.buttonText)}
+        />
 
-				<SettingSwitch
-					title="AUTO REGISTER"
-					subtitle="Automatically register new users after signing in with OAuth"
-					bind:checked={oauthConfig.autoRegister}
-					disabled={!oauthConfig.enabled}
-				/>
+        <SettingSwitch
+          title="AUTO REGISTER"
+          subtitle="Automatically register new users after signing in with OAuth"
+          bind:checked={oauthConfig.autoRegister}
+          disabled={!oauthConfig.enabled}
+        />
 
-				<SettingSwitch
-					title="AUTO LAUNCH"
-					subtitle="Start the OAuth login flow automatically upon navigating to the login page"
-					disabled={!oauthConfig.enabled}
-					bind:checked={oauthConfig.autoLaunch}
-				/>
+        <SettingSwitch
+          title="AUTO LAUNCH"
+          subtitle="Start the OAuth login flow automatically upon navigating to the login page"
+          disabled={!oauthConfig.enabled}
+          bind:checked={oauthConfig.autoLaunch}
+        />
 
-				<SettingSwitch
-					title="MOBILE REDIRECT URI OVERRIDE"
-					subtitle="Enable when `app.immich:/` is an invalid redirect URI."
-					disabled={!oauthConfig.enabled}
-					on:click={() => handleToggleOverride()}
-					bind:checked={oauthConfig.mobileOverrideEnabled}
-				/>
+        <SettingSwitch
+          title="MOBILE REDIRECT URI OVERRIDE"
+          subtitle="Enable when `app.immich:/` is an invalid redirect URI."
+          disabled={!oauthConfig.enabled}
+          on:click={() => handleToggleOverride()}
+          bind:checked={oauthConfig.mobileOverrideEnabled}
+        />
 
-				{#if oauthConfig.mobileOverrideEnabled}
-					<SettingInputField
-						inputType={SettingInputFieldType.TEXT}
-						label="MOBILE REDIRECT URI"
-						bind:value={oauthConfig.mobileRedirectUri}
-						required={true}
-						disabled={!oauthConfig.enabled}
-						isEdited={!(oauthConfig.mobileRedirectUri == savedConfig.mobileRedirectUri)}
-					/>
-				{/if}
+        {#if oauthConfig.mobileOverrideEnabled}
+          <SettingInputField
+            inputType={SettingInputFieldType.TEXT}
+            label="MOBILE REDIRECT URI"
+            bind:value={oauthConfig.mobileRedirectUri}
+            required={true}
+            disabled={!oauthConfig.enabled}
+            isEdited={!(oauthConfig.mobileRedirectUri == savedConfig.mobileRedirectUri)}
+          />
+        {/if}
 
-				<SettingButtonsRow
-					on:reset={reset}
-					on:save={saveSetting}
-					on:reset-to-default={resetToDefault}
-					showResetToDefault={!isEqual(savedConfig, defaultConfig)}
-				/>
-			</form>
-		</div>
-	{/await}
+        <SettingButtonsRow
+          on:reset={reset}
+          on:save={saveSetting}
+          on:reset-to-default={resetToDefault}
+          showResetToDefault={!isEqual(savedConfig, defaultConfig)}
+        />
+      </form>
+    </div>
+  {/await}
 </div>
diff --git a/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte b/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte
index fb8f0ef7c9..0cab82c558 100644
--- a/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte
@@ -1,121 +1,118 @@
 <script lang="ts">
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { handleError } from '$lib/utils/handle-error';
-	import { api, SystemConfigPasswordLoginDto } from '@api';
-	import { isEqual } from 'lodash-es';
-	import { fade } from 'svelte/transition';
-	import ConfirmDisableLogin from '../confirm-disable-login.svelte';
-	import SettingButtonsRow from '../setting-buttons-row.svelte';
-	import SettingSwitch from '../setting-switch.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { handleError } from '$lib/utils/handle-error';
+  import { api, SystemConfigPasswordLoginDto } from '@api';
+  import { isEqual } from 'lodash-es';
+  import { fade } from 'svelte/transition';
+  import ConfirmDisableLogin from '../confirm-disable-login.svelte';
+  import SettingButtonsRow from '../setting-buttons-row.svelte';
+  import SettingSwitch from '../setting-switch.svelte';
 
-	export let passwordLoginConfig: SystemConfigPasswordLoginDto; // this is the config that is being edited
+  export let passwordLoginConfig: SystemConfigPasswordLoginDto; // this is the config that is being edited
 
-	let savedConfig: SystemConfigPasswordLoginDto;
-	let defaultConfig: SystemConfigPasswordLoginDto;
+  let savedConfig: SystemConfigPasswordLoginDto;
+  let defaultConfig: SystemConfigPasswordLoginDto;
 
-	async function getConfigs() {
-		[savedConfig, defaultConfig] = await Promise.all([
-			api.systemConfigApi.getConfig().then((res) => res.data.passwordLogin),
-			api.systemConfigApi.getDefaults().then((res) => res.data.passwordLogin)
-		]);
-	}
+  async function getConfigs() {
+    [savedConfig, defaultConfig] = await Promise.all([
+      api.systemConfigApi.getConfig().then((res) => res.data.passwordLogin),
+      api.systemConfigApi.getDefaults().then((res) => res.data.passwordLogin),
+    ]);
+  }
 
-	let isConfirmOpen = false;
-	let handleConfirm: (value: boolean) => void;
+  let isConfirmOpen = false;
+  let handleConfirm: (value: boolean) => void;
 
-	const openConfirmModal = () => {
-		return new Promise((resolve) => {
-			handleConfirm = (value: boolean) => {
-				isConfirmOpen = false;
-				resolve(value);
-			};
-			isConfirmOpen = true;
-		});
-	};
+  const openConfirmModal = () => {
+    return new Promise((resolve) => {
+      handleConfirm = (value: boolean) => {
+        isConfirmOpen = false;
+        resolve(value);
+      };
+      isConfirmOpen = true;
+    });
+  };
 
-	async function saveSetting() {
-		try {
-			const { data: current } = await api.systemConfigApi.getConfig();
+  async function saveSetting() {
+    try {
+      const { data: current } = await api.systemConfigApi.getConfig();
 
-			if (!current.oauth.enabled && current.passwordLogin.enabled && !passwordLoginConfig.enabled) {
-				const confirmed = await openConfirmModal();
-				if (!confirmed) {
-					return;
-				}
-			}
+      if (!current.oauth.enabled && current.passwordLogin.enabled && !passwordLoginConfig.enabled) {
+        const confirmed = await openConfirmModal();
+        if (!confirmed) {
+          return;
+        }
+      }
 
-			const { data: updated } = await api.systemConfigApi.updateConfig({
-				systemConfigDto: {
-					...current,
-					passwordLogin: passwordLoginConfig
-				}
-			});
+      const { data: updated } = await api.systemConfigApi.updateConfig({
+        systemConfigDto: {
+          ...current,
+          passwordLogin: passwordLoginConfig,
+        },
+      });
 
-			passwordLoginConfig = { ...updated.passwordLogin };
-			savedConfig = { ...updated.passwordLogin };
+      passwordLoginConfig = { ...updated.passwordLogin };
+      savedConfig = { ...updated.passwordLogin };
 
-			notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
-		} catch (error) {
-			handleError(error, 'Unable to save settings');
-		}
-	}
+      notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
+    } catch (error) {
+      handleError(error, 'Unable to save settings');
+    }
+  }
 
-	async function reset() {
-		const { data: resetConfig } = await api.systemConfigApi.getConfig();
+  async function reset() {
+    const { data: resetConfig } = await api.systemConfigApi.getConfig();
 
-		passwordLoginConfig = { ...resetConfig.passwordLogin };
-		savedConfig = { ...resetConfig.passwordLogin };
+    passwordLoginConfig = { ...resetConfig.passwordLogin };
+    savedConfig = { ...resetConfig.passwordLogin };
 
-		notificationController.show({
-			message: 'Reset settings to the recent saved settings',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset settings to the recent saved settings',
+      type: NotificationType.Info,
+    });
+  }
 
-	async function resetToDefault() {
-		const { data: configs } = await api.systemConfigApi.getDefaults();
+  async function resetToDefault() {
+    const { data: configs } = await api.systemConfigApi.getDefaults();
 
-		passwordLoginConfig = { ...configs.passwordLogin };
-		defaultConfig = { ...configs.passwordLogin };
+    passwordLoginConfig = { ...configs.passwordLogin };
+    defaultConfig = { ...configs.passwordLogin };
 
-		notificationController.show({
-			message: 'Reset password settings to default',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset password settings to default',
+      type: NotificationType.Info,
+    });
+  }
 </script>
 
 {#if isConfirmOpen}
-	<ConfirmDisableLogin
-		on:cancel={() => handleConfirm(false)}
-		on:confirm={() => handleConfirm(true)}
-	/>
+  <ConfirmDisableLogin on:cancel={() => handleConfirm(false)} on:confirm={() => handleConfirm(true)} />
 {/if}
 
 <div>
-	{#await getConfigs() then}
-		<div in:fade={{ duration: 500 }}>
-			<form autocomplete="off" on:submit|preventDefault>
-				<div class="flex flex-col gap-4 ml-4 mt-4">
-					<div class="ml-4">
-						<SettingSwitch
-							title="ENABLED"
-							subtitle="Login with email and password"
-							bind:checked={passwordLoginConfig.enabled}
-						/>
+  {#await getConfigs() then}
+    <div in:fade={{ duration: 500 }}>
+      <form autocomplete="off" on:submit|preventDefault>
+        <div class="flex flex-col gap-4 ml-4 mt-4">
+          <div class="ml-4">
+            <SettingSwitch
+              title="ENABLED"
+              subtitle="Login with email and password"
+              bind:checked={passwordLoginConfig.enabled}
+            />
 
-						<SettingButtonsRow
-							on:reset={reset}
-							on:save={saveSetting}
-							on:reset-to-default={resetToDefault}
-							showResetToDefault={!isEqual(savedConfig, defaultConfig)}
-						/>
-					</div>
-				</div>
-			</form>
-		</div>
-	{/await}
+            <SettingButtonsRow
+              on:reset={reset}
+              on:save={saveSetting}
+              on:reset-to-default={resetToDefault}
+              showResetToDefault={!isEqual(savedConfig, defaultConfig)}
+            />
+          </div>
+        </div>
+      </form>
+    </div>
+  {/await}
 </div>
diff --git a/web/src/lib/components/admin-page/settings/setting-accordion.svelte b/web/src/lib/components/admin-page/settings/setting-accordion.svelte
index 39b4071433..941426ba22 100644
--- a/web/src/lib/components/admin-page/settings/setting-accordion.svelte
+++ b/web/src/lib/components/admin-page/settings/setting-accordion.svelte
@@ -1,56 +1,56 @@
 <script lang="ts">
-	import { slide } from 'svelte/transition';
-	export let title: string;
-	export let subtitle = '';
+  import { slide } from 'svelte/transition';
+  export let title: string;
+  export let subtitle = '';
 
-	export let isOpen = false;
-	const toggle = () => (isOpen = !isOpen);
+  export let isOpen = false;
+  const toggle = () => (isOpen = !isOpen);
 </script>
 
 <div class="border-b-[1px] border-gray-200 dark:border-gray-700 py-4">
-	<div class="flex justify-between place-items-center">
-		<div>
-			<h2 class="font-medium text-immich-primary dark:text-immich-dark-primary">
-				{title}
-			</h2>
+  <div class="flex justify-between place-items-center">
+    <div>
+      <h2 class="font-medium text-immich-primary dark:text-immich-dark-primary">
+        {title}
+      </h2>
 
-			<p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
-		</div>
+      <p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
+    </div>
 
-		<button
-			on:click={toggle}
-			aria-expanded={isOpen}
-			class="immich-circle-icon-button hover:bg-immich-primary/10 dark:text-immich-dark-fg hover:dark:bg-immich-dark-primary/20 rounded-full p-3 flex place-items-center place-content-center transition-all"
-		>
-			<svg
-				style="tran"
-				width="20"
-				height="20"
-				fill="none"
-				stroke-linecap="round"
-				stroke-linejoin="round"
-				stroke-width="2"
-				viewBox="0 0 24 24"
-				stroke="currentColor"
-			>
-				<path d="M19 9l-7 7-7-7" />
-			</svg>
-		</button>
-	</div>
+    <button
+      on:click={toggle}
+      aria-expanded={isOpen}
+      class="immich-circle-icon-button hover:bg-immich-primary/10 dark:text-immich-dark-fg hover:dark:bg-immich-dark-primary/20 rounded-full p-3 flex place-items-center place-content-center transition-all"
+    >
+      <svg
+        style="tran"
+        width="20"
+        height="20"
+        fill="none"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        stroke="currentColor"
+      >
+        <path d="M19 9l-7 7-7-7" />
+      </svg>
+    </button>
+  </div>
 
-	{#if isOpen}
-		<ul transition:slide={{ duration: 250 }} class="mb-2 ml-4">
-			<slot />
-		</ul>
-	{/if}
+  {#if isOpen}
+    <ul transition:slide={{ duration: 250 }} class="mb-2 ml-4">
+      <slot />
+    </ul>
+  {/if}
 </div>
 
 <style>
-	svg {
-		transition: transform 0.2s ease-in;
-	}
+  svg {
+    transition: transform 0.2s ease-in;
+  }
 
-	[aria-expanded='true'] svg {
-		transform: rotate(0.5turn);
-	}
+  [aria-expanded='true'] svg {
+    transform: rotate(0.5turn);
+  }
 </style>
diff --git a/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte b/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte
index e82d9b86fd..d1b6aa4d90 100644
--- a/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte
+++ b/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte
@@ -1,26 +1,26 @@
 <script lang="ts">
-	import Button from '$lib/components/elements/buttons/button.svelte';
-	import { createEventDispatcher } from 'svelte';
+  import Button from '$lib/components/elements/buttons/button.svelte';
+  import { createEventDispatcher } from 'svelte';
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	export let showResetToDefault = true;
+  export let showResetToDefault = true;
 </script>
 
 <div class="flex justify-between gap-2 mt-8">
-	<div class="left">
-		{#if showResetToDefault}
-			<button
-				on:click={() => dispatch('reset-to-default')}
-				class="text-sm dark:text-immich-dark-primary hover:dark:text-immich-dark-primary/75 text-immich-primary hover:text-immich-primary/75 font-medium bg-none"
-			>
-				Reset to default
-			</button>
-		{/if}
-	</div>
+  <div class="left">
+    {#if showResetToDefault}
+      <button
+        on:click={() => dispatch('reset-to-default')}
+        class="text-sm dark:text-immich-dark-primary hover:dark:text-immich-dark-primary/75 text-immich-primary hover:text-immich-primary/75 font-medium bg-none"
+      >
+        Reset to default
+      </button>
+    {/if}
+  </div>
 
-	<div class="right">
-		<Button size="sm" color="gray" on:click={() => dispatch('reset')}>Reset</Button>
-		<Button size="sm" on:click={() => dispatch('save')}>Save</Button>
-	</div>
+  <div class="right">
+    <Button size="sm" color="gray" on:click={() => dispatch('reset')}>Reset</Button>
+    <Button size="sm" on:click={() => dispatch('save')}>Save</Button>
+  </div>
 </div>
diff --git a/web/src/lib/components/admin-page/settings/setting-input-field.svelte b/web/src/lib/components/admin-page/settings/setting-input-field.svelte
index 28ac4ca003..931fb896df 100644
--- a/web/src/lib/components/admin-page/settings/setting-input-field.svelte
+++ b/web/src/lib/components/admin-page/settings/setting-input-field.svelte
@@ -1,65 +1,65 @@
 <script lang="ts" context="module">
-	export enum SettingInputFieldType {
-		EMAIL = 'email',
-		TEXT = 'text',
-		NUMBER = 'number',
-		PASSWORD = 'password'
-	}
+  export enum SettingInputFieldType {
+    EMAIL = 'email',
+    TEXT = 'text',
+    NUMBER = 'number',
+    PASSWORD = 'password',
+  }
 </script>
 
 <script lang="ts">
-	import { quintOut } from 'svelte/easing';
-	import { fly } from 'svelte/transition';
+  import { quintOut } from 'svelte/easing';
+  import { fly } from 'svelte/transition';
 
-	export let inputType: SettingInputFieldType;
-	export let value: string | number;
-	export let label = '';
-	export let desc = '';
-	export let required = false;
-	export let disabled = false;
-	export let isEdited = false;
+  export let inputType: SettingInputFieldType;
+  export let value: string | number;
+  export let label = '';
+  export let desc = '';
+  export let required = false;
+  export let disabled = false;
+  export let isEdited = false;
 
-	const handleInput = (e: Event) => {
-		value = (e.target as HTMLInputElement).value;
-		if (inputType === SettingInputFieldType.NUMBER) {
-			value = Number(value) || 0;
-		}
-	};
+  const handleInput = (e: Event) => {
+    value = (e.target as HTMLInputElement).value;
+    if (inputType === SettingInputFieldType.NUMBER) {
+      value = Number(value) || 0;
+    }
+  };
 </script>
 
 <div class="w-full">
-	<div class={`flex place-items-center gap-1 h-[26px]`}>
-		<label class={`immich-form-label text-sm`} for={label}>{label}</label>
-		{#if required}
-			<div class="text-red-400">*</div>
-		{/if}
+  <div class={`flex place-items-center gap-1 h-[26px]`}>
+    <label class={`immich-form-label text-sm`} for={label}>{label}</label>
+    {#if required}
+      <div class="text-red-400">*</div>
+    {/if}
 
-		{#if isEdited}
-			<div
-				transition:fly={{ x: 10, duration: 200, easing: quintOut }}
-				class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
-			>
-				Unsaved change
-			</div>
-		{/if}
-	</div>
+    {#if isEdited}
+      <div
+        transition:fly={{ x: 10, duration: 200, easing: quintOut }}
+        class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
+      >
+        Unsaved change
+      </div>
+    {/if}
+  </div>
 
-	{#if desc}
-		<p class="immich-form-label text-xs pb-2" id="{label}-desc">
-			{desc}
-		</p>
-	{/if}
+  {#if desc}
+    <p class="immich-form-label text-xs pb-2" id="{label}-desc">
+      {desc}
+    </p>
+  {/if}
 
-	<input
-		class="immich-form-input pb-2 w-full"
-		aria-describedby={desc ? `${label}-desc` : undefined}
-		aria-labelledby="{label}-label"
-		id={label}
-		name={label}
-		type={inputType}
-		{required}
-		{value}
-		on:input={handleInput}
-		{disabled}
-	/>
+  <input
+    class="immich-form-input pb-2 w-full"
+    aria-describedby={desc ? `${label}-desc` : undefined}
+    aria-labelledby="{label}-label"
+    id={label}
+    name={label}
+    type={inputType}
+    {required}
+    {value}
+    on:input={handleInput}
+    {disabled}
+  />
 </div>
diff --git a/web/src/lib/components/admin-page/settings/setting-select.svelte b/web/src/lib/components/admin-page/settings/setting-select.svelte
index ed9e297a4b..2a40a910d8 100644
--- a/web/src/lib/components/admin-page/settings/setting-select.svelte
+++ b/web/src/lib/components/admin-page/settings/setting-select.svelte
@@ -1,49 +1,49 @@
 <script lang="ts">
-	import { quintOut } from 'svelte/easing';
-	import { fly } from 'svelte/transition';
+  import { quintOut } from 'svelte/easing';
+  import { fly } from 'svelte/transition';
 
-	export let value: string;
-	export let options: { value: string; text: string }[];
-	export let label = '';
-	export let desc = '';
-	export let name = '';
-	export let isEdited = false;
+  export let value: string;
+  export let options: { value: string; text: string }[];
+  export let label = '';
+  export let desc = '';
+  export let name = '';
+  export let isEdited = false;
 
-	const handleChange = (e: Event) => {
-		value = (e.target as HTMLInputElement).value;
-	};
+  const handleChange = (e: Event) => {
+    value = (e.target as HTMLInputElement).value;
+  };
 </script>
 
 <div class="w-full">
-	<div class={`flex place-items-center gap-1 h-[26px]`}>
-		<label class={`immich-form-label text-sm`} for="{name}-select">{label}</label>
+  <div class={`flex place-items-center gap-1 h-[26px]`}>
+    <label class={`immich-form-label text-sm`} for="{name}-select">{label}</label>
 
-		{#if isEdited}
-			<div
-				transition:fly={{ x: 10, duration: 200, easing: quintOut }}
-				class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
-			>
-				Unsaved change
-			</div>
-		{/if}
-	</div>
+    {#if isEdited}
+      <div
+        transition:fly={{ x: 10, duration: 200, easing: quintOut }}
+        class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
+      >
+        Unsaved change
+      </div>
+    {/if}
+  </div>
 
-	{#if desc}
-		<p class="immich-form-label text-xs pb-2" id="{name}-desc">
-			{desc}
-		</p>
-	{/if}
+  {#if desc}
+    <p class="immich-form-label text-xs pb-2" id="{name}-desc">
+      {desc}
+    </p>
+  {/if}
 
-	<select
-		class="immich-form-input pb-2 w-full"
-		aria-describedby={desc ? `${name}-desc` : undefined}
-		{name}
-		id="{name}-select"
-		bind:value
-		on:change={handleChange}
-	>
-		{#each options as option}
-			<option value={option.value}>{option.text}</option>
-		{/each}
-	</select>
+  <select
+    class="immich-form-input pb-2 w-full"
+    aria-describedby={desc ? `${name}-desc` : undefined}
+    {name}
+    id="{name}-select"
+    bind:value
+    on:change={handleChange}
+  >
+    {#each options as option}
+      <option value={option.value}>{option.text}</option>
+    {/each}
+  </select>
 </div>
diff --git a/web/src/lib/components/admin-page/settings/setting-switch.svelte b/web/src/lib/components/admin-page/settings/setting-switch.svelte
index 22c149c9eb..205ee0e249 100644
--- a/web/src/lib/components/admin-page/settings/setting-switch.svelte
+++ b/web/src/lib/components/admin-page/settings/setting-switch.svelte
@@ -1,96 +1,90 @@
 <script lang="ts">
-	import { quintOut } from 'svelte/easing';
-	import { fly } from 'svelte/transition';
+  import { quintOut } from 'svelte/easing';
+  import { fly } from 'svelte/transition';
 
-	export let title: string;
-	export let subtitle = '';
-	export let checked = false;
-	export let disabled = false;
-	export let isEdited = false;
+  export let title: string;
+  export let subtitle = '';
+  export let checked = false;
+  export let disabled = false;
+  export let isEdited = false;
 </script>
 
 <div class="flex justify-between place-items-center">
-	<div>
-		<div class="flex place-items-center gap-1 h-[26px]">
-			<label class="immich-form-label text-sm" for={title}>
-				{title}
-			</label>
-			{#if isEdited}
-				<div
-					transition:fly={{ x: 10, duration: 200, easing: quintOut }}
-					class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
-				>
-					Unsaved change
-				</div>
-			{/if}
-		</div>
+  <div>
+    <div class="flex place-items-center gap-1 h-[26px]">
+      <label class="immich-form-label text-sm" for={title}>
+        {title}
+      </label>
+      {#if isEdited}
+        <div
+          transition:fly={{ x: 10, duration: 200, easing: quintOut }}
+          class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
+        >
+          Unsaved change
+        </div>
+      {/if}
+    </div>
 
-		<p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
-	</div>
+    <p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
+  </div>
 
-	<label class="relative inline-block flex-none w-[36px] h-[10px]">
-		<input
-			class="opacity-0 w-0 h-0 disabled::cursor-not-allowed"
-			type="checkbox"
-			bind:checked
-			on:click
-			{disabled}
-		/>
+  <label class="relative inline-block flex-none w-[36px] h-[10px]">
+    <input class="opacity-0 w-0 h-0 disabled::cursor-not-allowed" type="checkbox" bind:checked on:click {disabled} />
 
-		{#if disabled}
-			<span class="slider-disable" />
-		{:else}
-			<span class="slider" />
-		{/if}
-	</label>
+    {#if disabled}
+      <span class="slider-disable" />
+    {:else}
+      <span class="slider" />
+    {/if}
+  </label>
 </div>
 
 <style>
-	.slider,
-	.slider-disable {
-		position: absolute;
-		cursor: pointer;
-		top: 0;
-		left: 0;
-		right: 0;
-		bottom: 0;
-		background-color: #ccc;
-		-webkit-transition: 0.4s;
-		transition: 0.4s;
-		border-radius: 34px;
-	}
+  .slider,
+  .slider-disable {
+    position: absolute;
+    cursor: pointer;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: #ccc;
+    -webkit-transition: 0.4s;
+    transition: 0.4s;
+    border-radius: 34px;
+  }
 
-	input:disabled {
-		cursor: not-allowed;
-	}
+  input:disabled {
+    cursor: not-allowed;
+  }
 
-	.slider:before,
-	.slider-disable:before {
-		position: absolute;
-		content: '';
-		height: 20px;
-		width: 20px;
-		left: 0px;
-		right: 0px;
-		bottom: -4px;
-		background-color: gray;
-		-webkit-transition: 0.4s;
-		transition: 0.4s;
-		border-radius: 50%;
-	}
+  .slider:before,
+  .slider-disable:before {
+    position: absolute;
+    content: '';
+    height: 20px;
+    width: 20px;
+    left: 0px;
+    right: 0px;
+    bottom: -4px;
+    background-color: gray;
+    -webkit-transition: 0.4s;
+    transition: 0.4s;
+    border-radius: 50%;
+  }
 
-	input:checked + .slider-disable {
-		background-color: gray;
-	}
+  input:checked + .slider-disable {
+    background-color: gray;
+  }
 
-	input:checked + .slider {
-		background-color: #adcbfa;
-	}
+  input:checked + .slider {
+    background-color: #adcbfa;
+  }
 
-	input:checked + .slider:before {
-		-webkit-transform: translateX(18px);
-		-ms-transform: translateX(18px);
-		transform: translateX(18px);
-		background-color: #4250af;
-	}
+  input:checked + .slider:before {
+    -webkit-transform: translateX(18px);
+    -ms-transform: translateX(18px);
+    transform: translateX(18px);
+    background-color: #4250af;
+  }
 </style>
diff --git a/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte b/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte
index eae24ffe80..a405d42d94 100644
--- a/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte
@@ -1,241 +1,224 @@
 <script lang="ts">
-	import {
-		api,
-		SystemConfigStorageTemplateDto,
-		SystemConfigTemplateStorageOptionDto,
-		UserResponseDto
-	} from '@api';
-	import * as luxon from 'luxon';
-	import handlebar from 'handlebars';
-	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
-	import { fade } from 'svelte/transition';
-	import SupportedDatetimePanel from './supported-datetime-panel.svelte';
-	import SupportedVariablesPanel from './supported-variables-panel.svelte';
-	import SettingButtonsRow from '../setting-buttons-row.svelte';
-	import { isEqual } from 'lodash-es';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
+  import { api, SystemConfigStorageTemplateDto, SystemConfigTemplateStorageOptionDto, UserResponseDto } from '@api';
+  import * as luxon from 'luxon';
+  import handlebar from 'handlebars';
+  import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
+  import { fade } from 'svelte/transition';
+  import SupportedDatetimePanel from './supported-datetime-panel.svelte';
+  import SupportedVariablesPanel from './supported-variables-panel.svelte';
+  import SettingButtonsRow from '../setting-buttons-row.svelte';
+  import { isEqual } from 'lodash-es';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
 
-	export let storageConfig: SystemConfigStorageTemplateDto;
-	export let user: UserResponseDto;
+  export let storageConfig: SystemConfigStorageTemplateDto;
+  export let user: UserResponseDto;
 
-	let savedConfig: SystemConfigStorageTemplateDto;
-	let defaultConfig: SystemConfigStorageTemplateDto;
-	let templateOptions: SystemConfigTemplateStorageOptionDto;
-	let selectedPreset = '';
+  let savedConfig: SystemConfigStorageTemplateDto;
+  let defaultConfig: SystemConfigStorageTemplateDto;
+  let templateOptions: SystemConfigTemplateStorageOptionDto;
+  let selectedPreset = '';
 
-	async function getConfigs() {
-		[savedConfig, defaultConfig, templateOptions] = await Promise.all([
-			api.systemConfigApi.getConfig().then((res) => res.data.storageTemplate),
-			api.systemConfigApi.getDefaults().then((res) => res.data.storageTemplate),
-			api.systemConfigApi.getStorageTemplateOptions().then((res) => res.data)
-		]);
+  async function getConfigs() {
+    [savedConfig, defaultConfig, templateOptions] = await Promise.all([
+      api.systemConfigApi.getConfig().then((res) => res.data.storageTemplate),
+      api.systemConfigApi.getDefaults().then((res) => res.data.storageTemplate),
+      api.systemConfigApi.getStorageTemplateOptions().then((res) => res.data),
+    ]);
 
-		selectedPreset = savedConfig.template;
-	}
+    selectedPreset = savedConfig.template;
+  }
 
-	const getSupportDateTimeFormat = async () => {
-		const { data } = await api.systemConfigApi.getStorageTemplateOptions();
-		return data;
-	};
+  const getSupportDateTimeFormat = async () => {
+    const { data } = await api.systemConfigApi.getStorageTemplateOptions();
+    return data;
+  };
 
-	$: parsedTemplate = () => {
-		try {
-			return renderTemplate(storageConfig.template);
-		} catch (error) {
-			return 'error';
-		}
-	};
+  $: parsedTemplate = () => {
+    try {
+      return renderTemplate(storageConfig.template);
+    } catch (error) {
+      return 'error';
+    }
+  };
 
-	const renderTemplate = (templateString: string) => {
-		const template = handlebar.compile(templateString, {
-			knownHelpers: undefined
-		});
+  const renderTemplate = (templateString: string) => {
+    const template = handlebar.compile(templateString, {
+      knownHelpers: undefined,
+    });
 
-		const substitutions: Record<string, string> = {
-			filename: 'IMAGE_56437',
-			ext: 'jpg',
-			filetype: 'IMG',
-			filetypefull: 'IMAGE'
-		};
+    const substitutions: Record<string, string> = {
+      filename: 'IMAGE_56437',
+      ext: 'jpg',
+      filetype: 'IMG',
+      filetypefull: 'IMAGE',
+    };
 
-		const dt = luxon.DateTime.fromISO(new Date('2022-09-04T20:03:05.250').toISOString());
+    const dt = luxon.DateTime.fromISO(new Date('2022-09-04T20:03:05.250').toISOString());
 
-		const dateTokens = [
-			...templateOptions.yearOptions,
-			...templateOptions.monthOptions,
-			...templateOptions.dayOptions,
-			...templateOptions.hourOptions,
-			...templateOptions.minuteOptions,
-			...templateOptions.secondOptions
-		];
+    const dateTokens = [
+      ...templateOptions.yearOptions,
+      ...templateOptions.monthOptions,
+      ...templateOptions.dayOptions,
+      ...templateOptions.hourOptions,
+      ...templateOptions.minuteOptions,
+      ...templateOptions.secondOptions,
+    ];
 
-		for (const token of dateTokens) {
-			substitutions[token] = dt.toFormat(token);
-		}
+    for (const token of dateTokens) {
+      substitutions[token] = dt.toFormat(token);
+    }
 
-		return template(substitutions);
-	};
+    return template(substitutions);
+  };
 
-	async function reset() {
-		const { data: resetConfig } = await api.systemConfigApi.getConfig();
+  async function reset() {
+    const { data: resetConfig } = await api.systemConfigApi.getConfig();
 
-		storageConfig.template = resetConfig.storageTemplate.template;
-		savedConfig.template = resetConfig.storageTemplate.template;
+    storageConfig.template = resetConfig.storageTemplate.template;
+    savedConfig.template = resetConfig.storageTemplate.template;
 
-		notificationController.show({
-			message: 'Reset storage template settings to the recent saved settings',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset storage template settings to the recent saved settings',
+      type: NotificationType.Info,
+    });
+  }
 
-	async function saveSetting() {
-		try {
-			const { data: currentConfig } = await api.systemConfigApi.getConfig();
+  async function saveSetting() {
+    try {
+      const { data: currentConfig } = await api.systemConfigApi.getConfig();
 
-			const result = await api.systemConfigApi.updateConfig({
-				systemConfigDto: {
-					...currentConfig,
-					storageTemplate: storageConfig
-				}
-			});
+      const result = await api.systemConfigApi.updateConfig({
+        systemConfigDto: {
+          ...currentConfig,
+          storageTemplate: storageConfig,
+        },
+      });
 
-			storageConfig.template = result.data.storageTemplate.template;
-			savedConfig.template = result.data.storageTemplate.template;
+      storageConfig.template = result.data.storageTemplate.template;
+      savedConfig.template = result.data.storageTemplate.template;
 
-			notificationController.show({
-				message: 'Storage template saved',
-				type: NotificationType.Info
-			});
-		} catch (e) {
-			console.error('Error [storage-template-settings] [saveSetting]', e);
-			notificationController.show({
-				message: 'Unable to save settings',
-				type: NotificationType.Error
-			});
-		}
-	}
+      notificationController.show({
+        message: 'Storage template saved',
+        type: NotificationType.Info,
+      });
+    } catch (e) {
+      console.error('Error [storage-template-settings] [saveSetting]', e);
+      notificationController.show({
+        message: 'Unable to save settings',
+        type: NotificationType.Error,
+      });
+    }
+  }
 
-	async function resetToDefault() {
-		const { data: defaultConfig } = await api.systemConfigApi.getDefaults();
+  async function resetToDefault() {
+    const { data: defaultConfig } = await api.systemConfigApi.getDefaults();
 
-		storageConfig.template = defaultConfig.storageTemplate.template;
+    storageConfig.template = defaultConfig.storageTemplate.template;
 
-		notificationController.show({
-			message: 'Reset storage template to default',
-			type: NotificationType.Info
-		});
-	}
+    notificationController.show({
+      message: 'Reset storage template to default',
+      type: NotificationType.Info,
+    });
+  }
 
-	const handlePresetSelection = () => {
-		storageConfig.template = selectedPreset;
-	};
+  const handlePresetSelection = () => {
+    storageConfig.template = selectedPreset;
+  };
 </script>
 
 <section class="dark:text-immich-dark-fg">
-	{#await getConfigs() then}
-		<div id="directory-path-builder" class="m-4">
-			<h3 class="font-medium text-immich-primary dark:text-immich-dark-primary text-base">
-				Variables
-			</h3>
+  {#await getConfigs() then}
+    <div id="directory-path-builder" class="m-4">
+      <h3 class="font-medium text-immich-primary dark:text-immich-dark-primary text-base">Variables</h3>
 
-			<section class="support-date">
-				{#await getSupportDateTimeFormat()}
-					<LoadingSpinner />
-				{:then options}
-					<div transition:fade={{ duration: 200 }}>
-						<SupportedDatetimePanel {options} />
-					</div>
-				{/await}
-			</section>
+      <section class="support-date">
+        {#await getSupportDateTimeFormat()}
+          <LoadingSpinner />
+        {:then options}
+          <div transition:fade={{ duration: 200 }}>
+            <SupportedDatetimePanel {options} />
+          </div>
+        {/await}
+      </section>
 
-			<section class="support-date">
-				<SupportedVariablesPanel />
-			</section>
+      <section class="support-date">
+        <SupportedVariablesPanel />
+      </section>
 
-			<div class="mt-4 flex flex-col">
-				<h3 class="font-medium text-immich-primary dark:text-immich-dark-primary text-base">
-					Template
-				</h3>
+      <div class="mt-4 flex flex-col">
+        <h3 class="font-medium text-immich-primary dark:text-immich-dark-primary text-base">Template</h3>
 
-				<div class="text-xs my-2">
-					<h4>PREVIEW</h4>
-				</div>
+        <div class="text-xs my-2">
+          <h4>PREVIEW</h4>
+        </div>
 
-				<p class="text-xs">
-					Approximately path length limit : <span
-						class="font-semibold text-immich-primary dark:text-immich-dark-primary"
-						>{parsedTemplate().length + user.id.length + 'UPLOAD_LOCATION'.length}</span
-					>/260
-				</p>
+        <p class="text-xs">
+          Approximately path length limit : <span
+            class="font-semibold text-immich-primary dark:text-immich-dark-primary"
+            >{parsedTemplate().length + user.id.length + 'UPLOAD_LOCATION'.length}</span
+          >/260
+        </p>
 
-				<p class="text-xs">
-					<code>{user.storageLabel || user.id}</code> is the user's Storage Label
-				</p>
+        <p class="text-xs">
+          <code>{user.storageLabel || user.id}</code> is the user's Storage Label
+        </p>
 
-				<p
-					class="text-xs p-4 bg-gray-200 dark:bg-gray-700 dark:text-immich-dark-fg py-2 rounded-lg mt-2"
-				>
-					<span class="text-immich-fg/25 dark:text-immich-dark-fg/50"
-						>UPLOAD_LOCATION/{user.storageLabel || user.id}</span
-					>/{parsedTemplate()}.jpg
-				</p>
+        <p class="text-xs p-4 bg-gray-200 dark:bg-gray-700 dark:text-immich-dark-fg py-2 rounded-lg mt-2">
+          <span class="text-immich-fg/25 dark:text-immich-dark-fg/50"
+            >UPLOAD_LOCATION/{user.storageLabel || user.id}</span
+          >/{parsedTemplate()}.jpg
+        </p>
 
-				<form autocomplete="off" class="flex flex-col" on:submit|preventDefault>
-					<div class="flex flex-col my-2">
-						<label class="text-xs" for="presets">PRESET</label>
-						<select
-							class="text-sm bg-slate-200 p-2 rounded-lg mt-2 dark:bg-gray-600 hover:cursor-pointer"
-							name="presets"
-							id="preset-select"
-							bind:value={selectedPreset}
-							on:change={handlePresetSelection}
-						>
-							{#each templateOptions.presetOptions as preset}
-								<option value={preset}>{renderTemplate(preset)}</option>
-							{/each}
-						</select>
-					</div>
-					<div class="flex gap-2 align-bottom">
-						<SettingInputField
-							label="TEMPLATE"
-							required
-							inputType={SettingInputFieldType.TEXT}
-							bind:value={storageConfig.template}
-							isEdited={!(storageConfig.template === savedConfig.template)}
-						/>
+        <form autocomplete="off" class="flex flex-col" on:submit|preventDefault>
+          <div class="flex flex-col my-2">
+            <label class="text-xs" for="presets">PRESET</label>
+            <select
+              class="text-sm bg-slate-200 p-2 rounded-lg mt-2 dark:bg-gray-600 hover:cursor-pointer"
+              name="presets"
+              id="preset-select"
+              bind:value={selectedPreset}
+              on:change={handlePresetSelection}
+            >
+              {#each templateOptions.presetOptions as preset}
+                <option value={preset}>{renderTemplate(preset)}</option>
+              {/each}
+            </select>
+          </div>
+          <div class="flex gap-2 align-bottom">
+            <SettingInputField
+              label="TEMPLATE"
+              required
+              inputType={SettingInputFieldType.TEXT}
+              bind:value={storageConfig.template}
+              isEdited={!(storageConfig.template === savedConfig.template)}
+            />
 
-						<div class="flex-0">
-							<SettingInputField
-								label="EXTENSION"
-								inputType={SettingInputFieldType.TEXT}
-								value={'.jpg'}
-								disabled
-							/>
-						</div>
-					</div>
+            <div class="flex-0">
+              <SettingInputField label="EXTENSION" inputType={SettingInputFieldType.TEXT} value={'.jpg'} disabled />
+            </div>
+          </div>
 
-					<div id="migration-info" class="text-sm mt-4">
-						<p>
-							Template changes will only apply to new assets. To retroactively apply the template to
-							previously uploaded assets, run the <a
-								href="/admin/jobs-status"
-								class="text-immich-primary dark:text-immich-dark-primary">Storage Migration Job</a
-							>
-						</p>
-					</div>
+          <div id="migration-info" class="text-sm mt-4">
+            <p>
+              Template changes will only apply to new assets. To retroactively apply the template to previously uploaded
+              assets, run the <a href="/admin/jobs-status" class="text-immich-primary dark:text-immich-dark-primary"
+                >Storage Migration Job</a
+              >
+            </p>
+          </div>
 
-					<SettingButtonsRow
-						on:reset={reset}
-						on:save={saveSetting}
-						on:reset-to-default={resetToDefault}
-						showResetToDefault={!isEqual(savedConfig, defaultConfig)}
-					/>
-				</form>
-			</div>
-		</div>
-	{/await}
+          <SettingButtonsRow
+            on:reset={reset}
+            on:save={saveSetting}
+            on:reset-to-default={resetToDefault}
+            showResetToDefault={!isEqual(savedConfig, defaultConfig)}
+          />
+        </form>
+      </div>
+    </div>
+  {/await}
 </section>
diff --git a/web/src/lib/components/admin-page/settings/storage-template/supported-datetime-panel.svelte b/web/src/lib/components/admin-page/settings/storage-template/supported-datetime-panel.svelte
index d30798fb18..587ef3e0fc 100644
--- a/web/src/lib/components/admin-page/settings/storage-template/supported-datetime-panel.svelte
+++ b/web/src/lib/components/admin-page/settings/storage-template/supported-datetime-panel.svelte
@@ -1,78 +1,76 @@
 <script lang="ts">
-	import type { SystemConfigTemplateStorageOptionDto } from '@api';
-	import * as luxon from 'luxon';
+  import type { SystemConfigTemplateStorageOptionDto } from '@api';
+  import * as luxon from 'luxon';
 
-	export let options: SystemConfigTemplateStorageOptionDto;
+  export let options: SystemConfigTemplateStorageOptionDto;
 
-	const getLuxonExample = (format: string) => {
-		return luxon.DateTime.fromISO(new Date('2022-09-04T20:03:05.250').toISOString()).toFormat(
-			format
-		);
-	};
+  const getLuxonExample = (format: string) => {
+    return luxon.DateTime.fromISO(new Date('2022-09-04T20:03:05.250').toISOString()).toFormat(format);
+  };
 </script>
 
 <div class="text-xs mt-2">
-	<h4>DATE & TIME</h4>
+  <h4>DATE & TIME</h4>
 </div>
 
 <div class="text-xs bg-gray-200 dark:bg-gray-700 dark:text-immich-dark-fg p-4 mt-2 rounded-lg">
-	<div class="mb-2 text-gray-600 dark:text-immich-dark-fg">
-		<p>Asset's creation timestamp is used for the datetime information</p>
-		<p>Sample time 2022-09-04T20:03:05.250</p>
-	</div>
-	<div class="flex gap-[50px]">
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">YEAR</p>
-			<ul>
-				{#each options.yearOptions as yearFormat}
-					<li>{'{{'}{yearFormat}{'}}'} - {getLuxonExample(yearFormat)}</li>
-				{/each}
-			</ul>
-		</div>
+  <div class="mb-2 text-gray-600 dark:text-immich-dark-fg">
+    <p>Asset's creation timestamp is used for the datetime information</p>
+    <p>Sample time 2022-09-04T20:03:05.250</p>
+  </div>
+  <div class="flex gap-[50px]">
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">YEAR</p>
+      <ul>
+        {#each options.yearOptions as yearFormat}
+          <li>{'{{'}{yearFormat}{'}}'} - {getLuxonExample(yearFormat)}</li>
+        {/each}
+      </ul>
+    </div>
 
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">MONTH</p>
-			<ul>
-				{#each options.monthOptions as monthFormat}
-					<li>{'{{'}{monthFormat}{'}}'} - {getLuxonExample(monthFormat)}</li>
-				{/each}
-			</ul>
-		</div>
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">MONTH</p>
+      <ul>
+        {#each options.monthOptions as monthFormat}
+          <li>{'{{'}{monthFormat}{'}}'} - {getLuxonExample(monthFormat)}</li>
+        {/each}
+      </ul>
+    </div>
 
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">DAY</p>
-			<ul>
-				{#each options.dayOptions as dayFormat}
-					<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
-				{/each}
-			</ul>
-		</div>
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">DAY</p>
+      <ul>
+        {#each options.dayOptions as dayFormat}
+          <li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
+        {/each}
+      </ul>
+    </div>
 
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">HOUR</p>
-			<ul>
-				{#each options.hourOptions as dayFormat}
-					<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
-				{/each}
-			</ul>
-		</div>
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">HOUR</p>
+      <ul>
+        {#each options.hourOptions as dayFormat}
+          <li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
+        {/each}
+      </ul>
+    </div>
 
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">MINUTE</p>
-			<ul>
-				{#each options.minuteOptions as dayFormat}
-					<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
-				{/each}
-			</ul>
-		</div>
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">MINUTE</p>
+      <ul>
+        {#each options.minuteOptions as dayFormat}
+          <li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
+        {/each}
+      </ul>
+    </div>
 
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">SECOND</p>
-			<ul>
-				{#each options.secondOptions as dayFormat}
-					<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
-				{/each}
-			</ul>
-		</div>
-	</div>
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">SECOND</p>
+      <ul>
+        {#each options.secondOptions as dayFormat}
+          <li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
+        {/each}
+      </ul>
+    </div>
+  </div>
 </div>
diff --git a/web/src/lib/components/admin-page/settings/storage-template/supported-variables-panel.svelte b/web/src/lib/components/admin-page/settings/storage-template/supported-variables-panel.svelte
index a65c2bd7e6..e9d815aa6d 100644
--- a/web/src/lib/components/admin-page/settings/storage-template/supported-variables-panel.svelte
+++ b/web/src/lib/components/admin-page/settings/storage-template/supported-variables-panel.svelte
@@ -1,29 +1,29 @@
 <div class="text-xs mt-4">
-	<h4>OTHER VARIABLES</h4>
+  <h4>OTHER VARIABLES</h4>
 </div>
 
 <div class="text-xs bg-gray-200 dark:bg-gray-700 dark:text-immich-dark-fg p-4 mt-2 rounded-lg">
-	<div class="flex gap-[50px]">
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE NAME</p>
-			<ul>
-				<li>{`{{filename}}`}</li>
-			</ul>
-		</div>
+  <div class="flex gap-[50px]">
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE NAME</p>
+      <ul>
+        <li>{`{{filename}}`}</li>
+      </ul>
+    </div>
 
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE EXTENSION</p>
-			<ul>
-				<li>{`{{ext}}`}</li>
-			</ul>
-		</div>
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE EXTENSION</p>
+      <ul>
+        <li>{`{{ext}}`}</li>
+      </ul>
+    </div>
 
-		<div>
-			<p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE TYPE</p>
-			<ul>
-				<li>{`{{filetype}}`} - VID or IMG</li>
-				<li>{`{{filetypefull}}`} - VIDEO or IMAGE</li>
-			</ul>
-		</div>
-	</div>
+    <div>
+      <p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE TYPE</p>
+      <ul>
+        <li>{`{{filetype}}`} - VID or IMG</li>
+        <li>{`{{filetypefull}}`} - VIDEO or IMAGE</li>
+      </ul>
+    </div>
+  </div>
 </div>
diff --git a/web/src/lib/components/album-page/__tests__/album-card.spec.ts b/web/src/lib/components/album-page/__tests__/album-card.spec.ts
index ba7b194c58..33c8c48dd9 100644
--- a/web/src/lib/components/album-page/__tests__/album-card.spec.ts
+++ b/web/src/lib/components/album-page/__tests__/album-card.spec.ts
@@ -1,141 +1,136 @@
-import { jest, describe, it } from '@jest/globals';
-import { render, RenderResult, waitFor, fireEvent } from '@testing-library/svelte';
 import { createObjectURLMock } from '$lib/__mocks__/jsdom-url.mock';
 import { api, ThumbnailFormat } from '@api';
+import { describe, it, jest } from '@jest/globals';
 import { albumFactory } from '@test-data';
-import AlbumCard from '../album-card.svelte';
 import '@testing-library/jest-dom';
+import { fireEvent, render, RenderResult, waitFor } from '@testing-library/svelte';
+import AlbumCard from '../album-card.svelte';
 
 jest.mock('@api');
 
 const apiMock: jest.MockedObject<typeof api> = api as jest.MockedObject<typeof api>;
 
 describe('AlbumCard component', () => {
-	let sut: RenderResult<AlbumCard>;
+  let sut: RenderResult<AlbumCard>;
 
-	it.each([
-		{
-			album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 0 }),
-			count: 0,
-			shared: false
-		},
-		{
-			album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 0 }),
-			count: 0,
-			shared: true
-		},
-		{
-			album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 5 }),
-			count: 5,
-			shared: false
-		},
-		{
-			album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 2 }),
-			count: 2,
-			shared: true
-		}
-	])(
-		'shows album data without thumbnail with count $count - shared: $shared',
-		async ({ album, count, shared }) => {
-			sut = render(AlbumCard, { album, user: album.owner });
+  it.each([
+    {
+      album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 0 }),
+      count: 0,
+      shared: false,
+    },
+    {
+      album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 0 }),
+      count: 0,
+      shared: true,
+    },
+    {
+      album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 5 }),
+      count: 5,
+      shared: false,
+    },
+    {
+      album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 2 }),
+      count: 2,
+      shared: true,
+    },
+  ])('shows album data without thumbnail with count $count - shared: $shared', async ({ album, count, shared }) => {
+    sut = render(AlbumCard, { album, user: album.owner });
 
-			const albumImgElement = sut.getByTestId('album-image');
-			const albumNameElement = sut.getByTestId('album-name');
-			const albumDetailsElement = sut.getByTestId('album-details');
-			const detailsText = `${count} items` + (shared ? ' . Shared' : '');
+    const albumImgElement = sut.getByTestId('album-image');
+    const albumNameElement = sut.getByTestId('album-name');
+    const albumDetailsElement = sut.getByTestId('album-details');
+    const detailsText = `${count} items` + (shared ? ' . Shared' : '');
 
-			expect(albumImgElement).toHaveAttribute('src');
-			expect(albumImgElement).toHaveAttribute('alt', album.id);
+    expect(albumImgElement).toHaveAttribute('src');
+    expect(albumImgElement).toHaveAttribute('alt', album.id);
 
-			await waitFor(() => expect(albumImgElement).toHaveAttribute('src'));
+    await waitFor(() => expect(albumImgElement).toHaveAttribute('src'));
 
-			expect(albumImgElement).toHaveAttribute('alt', album.id);
-			expect(apiMock.assetApi.getAssetThumbnail).not.toHaveBeenCalled();
+    expect(albumImgElement).toHaveAttribute('alt', album.id);
+    expect(apiMock.assetApi.getAssetThumbnail).not.toHaveBeenCalled();
 
-			expect(albumNameElement).toHaveTextContent(album.albumName);
-			expect(albumDetailsElement).toHaveTextContent(new RegExp(detailsText));
-		}
-	);
+    expect(albumNameElement).toHaveTextContent(album.albumName);
+    expect(albumDetailsElement).toHaveTextContent(new RegExp(detailsText));
+  });
 
-	it('shows album data and and loads the thumbnail image when available', async () => {
-		const thumbnailFile = new File([new Blob()], 'fileThumbnail');
-		const thumbnailUrl = 'blob:thumbnailUrlOne';
-		apiMock.assetApi.getAssetThumbnail.mockResolvedValue({
-			data: thumbnailFile,
-			config: {},
-			headers: {},
-			status: 200,
-			statusText: ''
-		});
-		createObjectURLMock.mockReturnValueOnce(thumbnailUrl);
+  it('shows album data and and loads the thumbnail image when available', async () => {
+    const thumbnailFile = new File([new Blob()], 'fileThumbnail');
+    const thumbnailUrl = 'blob:thumbnailUrlOne';
+    apiMock.assetApi.getAssetThumbnail.mockResolvedValue({
+      data: thumbnailFile,
+      config: {},
+      headers: {},
+      status: 200,
+      statusText: '',
+    });
+    createObjectURLMock.mockReturnValueOnce(thumbnailUrl);
 
-		const album = albumFactory.build({
-			albumThumbnailAssetId: 'thumbnailIdOne',
-			shared: false,
-			albumName: 'some album name'
-		});
-		sut = render(AlbumCard, { album, user: album.owner });
+    const album = albumFactory.build({
+      albumThumbnailAssetId: 'thumbnailIdOne',
+      shared: false,
+      albumName: 'some album name',
+    });
+    sut = render(AlbumCard, { album, user: album.owner });
 
-		const albumImgElement = sut.getByTestId('album-image');
-		const albumNameElement = sut.getByTestId('album-name');
-		const albumDetailsElement = sut.getByTestId('album-details');
-		expect(albumImgElement).toHaveAttribute('alt', album.id);
+    const albumImgElement = sut.getByTestId('album-image');
+    const albumNameElement = sut.getByTestId('album-name');
+    const albumDetailsElement = sut.getByTestId('album-details');
+    expect(albumImgElement).toHaveAttribute('alt', album.id);
 
-		await waitFor(() => expect(albumImgElement).toHaveAttribute('src', thumbnailUrl));
+    await waitFor(() => expect(albumImgElement).toHaveAttribute('src', thumbnailUrl));
 
-		expect(albumImgElement).toHaveAttribute('alt', album.id);
-		expect(apiMock.assetApi.getAssetThumbnail).toHaveBeenCalledTimes(1);
-		expect(apiMock.assetApi.getAssetThumbnail).toHaveBeenCalledWith(
-			{
-				id: 'thumbnailIdOne',
-				format: ThumbnailFormat.Jpeg
-			},
-			{ responseType: 'blob' }
-		);
-		expect(createObjectURLMock).toHaveBeenCalledWith(thumbnailFile);
+    expect(albumImgElement).toHaveAttribute('alt', album.id);
+    expect(apiMock.assetApi.getAssetThumbnail).toHaveBeenCalledTimes(1);
+    expect(apiMock.assetApi.getAssetThumbnail).toHaveBeenCalledWith(
+      {
+        id: 'thumbnailIdOne',
+        format: ThumbnailFormat.Jpeg,
+      },
+      { responseType: 'blob' },
+    );
+    expect(createObjectURLMock).toHaveBeenCalledWith(thumbnailFile);
 
-		expect(albumNameElement).toHaveTextContent('some album name');
-		expect(albumDetailsElement).toHaveTextContent('0 items');
-	});
+    expect(albumNameElement).toHaveTextContent('some album name');
+    expect(albumDetailsElement).toHaveTextContent('0 items');
+  });
 
-	describe('with rendered component - no thumbnail', () => {
-		const album = Object.freeze(albumFactory.build({ albumThumbnailAssetId: null }));
+  describe('with rendered component - no thumbnail', () => {
+    const album = Object.freeze(albumFactory.build({ albumThumbnailAssetId: null }));
 
-		beforeEach(async () => {
-			sut = render(AlbumCard, { album, user: album.owner });
+    beforeEach(async () => {
+      sut = render(AlbumCard, { album, user: album.owner });
 
-			const albumImgElement = sut.getByTestId('album-image');
-			await waitFor(() => expect(albumImgElement).toHaveAttribute('src'));
-		});
+      const albumImgElement = sut.getByTestId('album-image');
+      await waitFor(() => expect(albumImgElement).toHaveAttribute('src'));
+    });
 
-		it('dispatches custom "click" event with the album in context', async () => {
-			const onClickHandler = jest.fn();
-			sut.component.$on('click', onClickHandler);
-			const albumCardElement = sut.getByTestId('album-card');
+    it('dispatches custom "click" event with the album in context', async () => {
+      const onClickHandler = jest.fn();
+      sut.component.$on('click', onClickHandler);
+      const albumCardElement = sut.getByTestId('album-card');
 
-			await fireEvent.click(albumCardElement);
-			expect(onClickHandler).toHaveBeenCalledTimes(1);
-			expect(onClickHandler).toHaveBeenCalledWith(expect.objectContaining({ detail: album }));
-		});
+      await fireEvent.click(albumCardElement);
+      expect(onClickHandler).toHaveBeenCalledTimes(1);
+      expect(onClickHandler).toHaveBeenCalledWith(expect.objectContaining({ detail: album }));
+    });
 
-		it('dispatches custom "click" event on context menu click with mouse coordinates', async () => {
-			const onClickHandler = jest.fn();
-			sut.component.$on('showalbumcontextmenu', onClickHandler);
+    it('dispatches custom "click" event on context menu click with mouse coordinates', async () => {
+      const onClickHandler = jest.fn();
+      sut.component.$on('showalbumcontextmenu', onClickHandler);
 
-			const contextMenuBtnParent = sut.getByTestId('context-button-parent');
+      const contextMenuBtnParent = sut.getByTestId('context-button-parent');
 
-			await fireEvent(
-				contextMenuBtnParent,
-				new MouseEvent('click', {
-					clientX: 123,
-					clientY: 456
-				})
-			);
+      await fireEvent(
+        contextMenuBtnParent,
+        new MouseEvent('click', {
+          clientX: 123,
+          clientY: 456,
+        }),
+      );
 
-			expect(onClickHandler).toHaveBeenCalledTimes(1);
-			expect(onClickHandler).toHaveBeenCalledWith(
-				expect.objectContaining({ detail: { x: 123, y: 456 } })
-			);
-		});
-	});
+      expect(onClickHandler).toHaveBeenCalledTimes(1);
+      expect(onClickHandler).toHaveBeenCalledWith(expect.objectContaining({ detail: { x: 123, y: 456 } }));
+    });
+  });
 });
diff --git a/web/src/lib/components/album-page/album-card.svelte b/web/src/lib/components/album-page/album-card.svelte
index 17ec886761..62f350f74a 100644
--- a/web/src/lib/components/album-page/album-card.svelte
+++ b/web/src/lib/components/album-page/album-card.svelte
@@ -1,133 +1,133 @@
 <script lang="ts">
-	import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
-	import { locale } from '$lib/stores/preferences.store';
-	import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api';
-	import { createEventDispatcher, onMount } from 'svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import IconButton from '../elements/buttons/icon-button.svelte';
-	import type { OnClick, OnShowContextMenu } from './album-card';
+  import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
+  import { locale } from '$lib/stores/preferences.store';
+  import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import IconButton from '../elements/buttons/icon-button.svelte';
+  import type { OnClick, OnShowContextMenu } from './album-card';
 
-	export let album: AlbumResponseDto;
-	export let isSharingView = false;
-	export let user: UserResponseDto;
-	export let showItemCount = true;
-	export let showContextMenu = true;
+  export let album: AlbumResponseDto;
+  export let isSharingView = false;
+  export let user: UserResponseDto;
+  export let showItemCount = true;
+  export let showContextMenu = true;
 
-	$: imageData = album.albumThumbnailAssetId
-		? api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
-		: noThumbnailUrl;
+  $: imageData = album.albumThumbnailAssetId
+    ? api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
+    : noThumbnailUrl;
 
-	const dispatchClick = createEventDispatcher<OnClick>();
-	const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
+  const dispatchClick = createEventDispatcher<OnClick>();
+  const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
 
-	const loadHighQualityThumbnail = async (thubmnailId: string | null) => {
-		if (thubmnailId == null) {
-			return;
-		}
+  const loadHighQualityThumbnail = async (thubmnailId: string | null) => {
+    if (thubmnailId == null) {
+      return;
+    }
 
-		const { data } = await api.assetApi.getAssetThumbnail(
-			{
-				id: thubmnailId,
-				format: ThumbnailFormat.Jpeg
-			},
-			{
-				responseType: 'blob'
-			}
-		);
+    const { data } = await api.assetApi.getAssetThumbnail(
+      {
+        id: thubmnailId,
+        format: ThumbnailFormat.Jpeg,
+      },
+      {
+        responseType: 'blob',
+      },
+    );
 
-		if (data instanceof Blob) {
-			return URL.createObjectURL(data);
-		}
-	};
+    if (data instanceof Blob) {
+      return URL.createObjectURL(data);
+    }
+  };
 
-	const showAlbumContextMenu = (e: MouseEvent) => {
-		dispatchShowContextMenu('showalbumcontextmenu', {
-			x: e.clientX,
-			y: e.clientY
-		});
-	};
+  const showAlbumContextMenu = (e: MouseEvent) => {
+    dispatchShowContextMenu('showalbumcontextmenu', {
+      x: e.clientX,
+      y: e.clientY,
+    });
+  };
 
-	onMount(async () => {
-		imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || noThumbnailUrl;
-	});
+  onMount(async () => {
+    imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || noThumbnailUrl;
+  });
 
-	const getAlbumOwnerInfo = async (): Promise<UserResponseDto> => {
-		const { data } = await api.userApi.getUserById({ userId: album.ownerId });
+  const getAlbumOwnerInfo = async (): Promise<UserResponseDto> => {
+    const { data } = await api.userApi.getUserById({ userId: album.ownerId });
 
-		return data;
-	};
+    return data;
+  };
 </script>
 
 <div
-	class="group hover:cursor-pointer mt-4 border-[3px] border-transparent dark:hover:border-immich-dark-primary/75 hover:border-immich-primary/75 rounded-3xl p-5 relative"
-	on:click={() => dispatchClick('click', album)}
-	on:keydown={() => dispatchClick('click', album)}
-	data-testid="album-card"
+  class="group hover:cursor-pointer mt-4 border-[3px] border-transparent dark:hover:border-immich-dark-primary/75 hover:border-immich-primary/75 rounded-3xl p-5 relative"
+  on:click={() => dispatchClick('click', album)}
+  on:keydown={() => dispatchClick('click', album)}
+  data-testid="album-card"
 >
-	<!-- svelte-ignore a11y-click-events-have-key-events -->
-	{#if showContextMenu}
-		<div
-			id={`icon-${album.id}`}
-			class="absolute top-6 right-6 z-10"
-			on:click|stopPropagation|preventDefault={showAlbumContextMenu}
-			data-testid="context-button-parent"
-		>
-			<IconButton color="overlay-primary">
-				<DotsVertical size="20" />
-			</IconButton>
-		</div>
-	{/if}
+  <!-- svelte-ignore a11y-click-events-have-key-events -->
+  {#if showContextMenu}
+    <div
+      id={`icon-${album.id}`}
+      class="absolute top-6 right-6 z-10"
+      on:click|stopPropagation|preventDefault={showAlbumContextMenu}
+      data-testid="context-button-parent"
+    >
+      <IconButton color="overlay-primary">
+        <DotsVertical size="20" />
+      </IconButton>
+    </div>
+  {/if}
 
-	<div class={`aspect-square relative`}>
-		<img
-			src={imageData}
-			alt={album.id}
-			class={`object-cover h-full w-full transition-all z-0 rounded-3xl duration-300 hover:shadow-lg`}
-			data-testid="album-image"
-			draggable="false"
-		/>
-		<div
-			class="w-full h-full absolute top-0 rounded-3xl {isSharingView
-				? 'group-hover:bg-yellow-800/25'
-				: 'group-hover:bg-indigo-800/25'} "
-		/>
-	</div>
+  <div class={`aspect-square relative`}>
+    <img
+      src={imageData}
+      alt={album.id}
+      class={`object-cover h-full w-full transition-all z-0 rounded-3xl duration-300 hover:shadow-lg`}
+      data-testid="album-image"
+      draggable="false"
+    />
+    <div
+      class="w-full h-full absolute top-0 rounded-3xl {isSharingView
+        ? 'group-hover:bg-yellow-800/25'
+        : 'group-hover:bg-indigo-800/25'} "
+    />
+  </div>
 
-	<div class="mt-4">
-		<p
-			class="text-xl font-semibold dark:text-immich-dark-primary text-immich-primary w-full truncate"
-			data-testid="album-name"
-			title={album.albumName}
-		>
-			{album.albumName}
-		</p>
+  <div class="mt-4">
+    <p
+      class="text-xl font-semibold dark:text-immich-dark-primary text-immich-primary w-full truncate"
+      data-testid="album-name"
+      title={album.albumName}
+    >
+      {album.albumName}
+    </p>
 
-		<span class="text-sm flex gap-2 dark:text-immich-dark-fg" data-testid="album-details">
-			{#if showItemCount}
-				<p>
-					{album.assetCount.toLocaleString($locale)}
-					{album.assetCount == 1 ? `item` : `items`}
-				</p>
-			{/if}
+    <span class="text-sm flex gap-2 dark:text-immich-dark-fg" data-testid="album-details">
+      {#if showItemCount}
+        <p>
+          {album.assetCount.toLocaleString($locale)}
+          {album.assetCount == 1 ? `item` : `items`}
+        </p>
+      {/if}
 
-			{#if isSharingView || album.shared}
-				<p>·</p>
-			{/if}
+      {#if isSharingView || album.shared}
+        <p>·</p>
+      {/if}
 
-			{#if isSharingView}
-				{#await getAlbumOwnerInfo() then albumOwner}
-					{#if user.email == albumOwner.email}
-						<p>Owned</p>
-					{:else}
-						<p>
-							Shared by {albumOwner.firstName}
-							{albumOwner.lastName}
-						</p>
-					{/if}
-				{/await}
-			{:else if album.shared}
-				<p>Shared</p>
-			{/if}
-		</span>
-	</div>
+      {#if isSharingView}
+        {#await getAlbumOwnerInfo() then albumOwner}
+          {#if user.email == albumOwner.email}
+            <p>Owned</p>
+          {:else}
+            <p>
+              Shared by {albumOwner.firstName}
+              {albumOwner.lastName}
+            </p>
+          {/if}
+        {/await}
+      {:else if album.shared}
+        <p>Shared</p>
+      {/if}
+    </span>
+  </div>
 </div>
diff --git a/web/src/lib/components/album-page/album-card.ts b/web/src/lib/components/album-page/album-card.ts
index c69b7e6c3a..4d395d3c5e 100644
--- a/web/src/lib/components/album-page/album-card.ts
+++ b/web/src/lib/components/album-page/album-card.ts
@@ -1,11 +1,11 @@
 import type { AlbumResponseDto } from '@api';
 
 export type OnShowContextMenu = {
-	showalbumcontextmenu: OnShowContextMenuDetail;
+  showalbumcontextmenu: OnShowContextMenuDetail;
 };
 
 export type OnClick = {
-	click: OnClickDetail;
+  click: OnClickDetail;
 };
 
 export type OnShowContextMenuDetail = { x: number; y: number };
diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte
index 4b0c28cc16..c56a579b9d 100644
--- a/web/src/lib/components/album-page/album-viewer.svelte
+++ b/web/src/lib/components/album-page/album-viewer.svelte
@@ -1,533 +1,490 @@
 <script lang="ts">
-	import { browser } from '$app/environment';
-	import { afterNavigate, goto } from '$app/navigation';
-	import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
-	import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
-	import { locale } from '$lib/stores/preferences.store';
-	import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
-	import {
-		AlbumResponseDto,
-		AssetResponseDto,
-		SharedLinkResponseDto,
-		SharedLinkType,
-		UserResponseDto,
-		api
-	} from '@api';
-	import { onMount } from 'svelte';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
-	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import DownloadAction from '../photos-page/actions/download-action.svelte';
-	import RemoveFromAlbum from '../photos-page/actions/remove-from-album.svelte';
-	import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
-	import UserAvatar from '../shared-components/user-avatar.svelte';
-	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
-	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
-	import ControlAppBar from '../shared-components/control-app-bar.svelte';
-	import CreateSharedLinkModal from '../shared-components/create-share-link-modal/create-shared-link-modal.svelte';
-	import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
-	import ImmichLogo from '../shared-components/immich-logo.svelte';
-	import SelectAll from 'svelte-material-icons/SelectAll.svelte';
-	import {
-		NotificationType,
-		notificationController
-	} from '../shared-components/notification/notification';
-	import ThemeButton from '../shared-components/theme-button.svelte';
-	import AssetSelection from './asset-selection.svelte';
-	import ShareInfoModal from './share-info-modal.svelte';
-	import ThumbnailSelection from './thumbnail-selection.svelte';
-	import UserSelectionModal from './user-selection-modal.svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
-	import { handleError } from '../../utils/handle-error';
-	import { downloadArchive } from '../../utils/asset-utils';
+  import { browser } from '$app/environment';
+  import { afterNavigate, goto } from '$app/navigation';
+  import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
+  import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
+  import { locale } from '$lib/stores/preferences.store';
+  import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
+  import {
+    AlbumResponseDto,
+    AssetResponseDto,
+    SharedLinkResponseDto,
+    SharedLinkType,
+    UserResponseDto,
+    api,
+  } from '@api';
+  import { onMount } from 'svelte';
+  import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+  import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
+  import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import DownloadAction from '../photos-page/actions/download-action.svelte';
+  import RemoveFromAlbum from '../photos-page/actions/remove-from-album.svelte';
+  import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
+  import UserAvatar from '../shared-components/user-avatar.svelte';
+  import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
+  import MenuOption from '../shared-components/context-menu/menu-option.svelte';
+  import ControlAppBar from '../shared-components/control-app-bar.svelte';
+  import CreateSharedLinkModal from '../shared-components/create-share-link-modal/create-shared-link-modal.svelte';
+  import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
+  import ImmichLogo from '../shared-components/immich-logo.svelte';
+  import SelectAll from 'svelte-material-icons/SelectAll.svelte';
+  import { NotificationType, notificationController } from '../shared-components/notification/notification';
+  import ThemeButton from '../shared-components/theme-button.svelte';
+  import AssetSelection from './asset-selection.svelte';
+  import ShareInfoModal from './share-info-modal.svelte';
+  import ThumbnailSelection from './thumbnail-selection.svelte';
+  import UserSelectionModal from './user-selection-modal.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import { handleError } from '../../utils/handle-error';
+  import { downloadArchive } from '../../utils/asset-utils';
 
-	export let album: AlbumResponseDto;
-	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
+  export let album: AlbumResponseDto;
+  export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 
-	const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
+  const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
 
-	let isShowAssetSelection = false;
+  let isShowAssetSelection = false;
 
-	let isShowShareLinkModal = false;
+  let isShowShareLinkModal = false;
 
-	$: $isAlbumAssetSelectionOpen = isShowAssetSelection;
-	$: {
-		if (browser) {
-			if (isShowAssetSelection) {
-				document.body.style.overflow = 'hidden';
-			} else {
-				document.body.style.overflow = 'auto';
-			}
-		}
-	}
-	let isShowShareUserSelection = false;
-	let isEditingTitle = false;
-	let isCreatingSharedAlbum = false;
-	let isShowShareInfoModal = false;
-	let isShowAlbumOptions = false;
-	let isShowThumbnailSelection = false;
-	let isShowDeleteConfirmation = false;
+  $: $isAlbumAssetSelectionOpen = isShowAssetSelection;
+  $: {
+    if (browser) {
+      if (isShowAssetSelection) {
+        document.body.style.overflow = 'hidden';
+      } else {
+        document.body.style.overflow = 'auto';
+      }
+    }
+  }
+  let isShowShareUserSelection = false;
+  let isEditingTitle = false;
+  let isCreatingSharedAlbum = false;
+  let isShowShareInfoModal = false;
+  let isShowAlbumOptions = false;
+  let isShowThumbnailSelection = false;
+  let isShowDeleteConfirmation = false;
 
-	let backUrl = '/albums';
-	let currentAlbumName = '';
-	let currentUser: UserResponseDto;
-	let titleInput: HTMLInputElement;
-	let contextMenuPosition = { x: 0, y: 0 };
+  let backUrl = '/albums';
+  let currentAlbumName = '';
+  let currentUser: UserResponseDto;
+  let titleInput: HTMLInputElement;
+  let contextMenuPosition = { x: 0, y: 0 };
 
-	$: isPublicShared = sharedLink;
-	$: isOwned = currentUser?.id == album.ownerId;
+  $: isPublicShared = sharedLink;
+  $: isOwned = currentUser?.id == album.ownerId;
 
-	dragAndDropFilesStore.subscribe((value) => {
-		if (value.isDragging && value.files.length > 0) {
-			fileUploadHandler(value.files, album.id, sharedLink?.key);
-			dragAndDropFilesStore.set({ isDragging: false, files: [] });
-		}
-	});
+  dragAndDropFilesStore.subscribe((value) => {
+    if (value.isDragging && value.files.length > 0) {
+      fileUploadHandler(value.files, album.id, sharedLink?.key);
+      dragAndDropFilesStore.set({ isDragging: false, files: [] });
+    }
+  });
 
-	let multiSelectAsset: Set<AssetResponseDto> = new Set();
-	$: isMultiSelectionMode = multiSelectAsset.size > 0;
+  let multiSelectAsset: Set<AssetResponseDto> = new Set();
+  $: isMultiSelectionMode = multiSelectAsset.size > 0;
 
-	afterNavigate(({ from }) => {
-		backUrl = from?.url.pathname ?? '/albums';
+  afterNavigate(({ from }) => {
+    backUrl = from?.url.pathname ?? '/albums';
 
-		if (from?.url.pathname === '/sharing' && album.sharedUsers.length === 0) {
-			isCreatingSharedAlbum = true;
-		}
+    if (from?.url.pathname === '/sharing' && album.sharedUsers.length === 0) {
+      isCreatingSharedAlbum = true;
+    }
 
-		if (from?.route.id === '/(user)/search') {
-			backUrl = from.url.href;
-		}
-	});
+    if (from?.route.id === '/(user)/search') {
+      backUrl = from.url.href;
+    }
+  });
 
-	const albumDateFormat: Intl.DateTimeFormatOptions = {
-		month: 'short',
-		day: 'numeric',
-		year: 'numeric'
-	};
+  const albumDateFormat: Intl.DateTimeFormatOptions = {
+    month: 'short',
+    day: 'numeric',
+    year: 'numeric',
+  };
 
-	const getDateRange = () => {
-		const startDate = new Date(album.assets[0].fileCreatedAt);
-		const endDate = new Date(album.assets[album.assetCount - 1].fileCreatedAt);
+  const getDateRange = () => {
+    const startDate = new Date(album.assets[0].fileCreatedAt);
+    const endDate = new Date(album.assets[album.assetCount - 1].fileCreatedAt);
 
-		const startDateString = startDate.toLocaleDateString($locale, albumDateFormat);
-		const endDateString = endDate.toLocaleDateString($locale, albumDateFormat);
+    const startDateString = startDate.toLocaleDateString($locale, albumDateFormat);
+    const endDateString = endDate.toLocaleDateString($locale, albumDateFormat);
 
-		// If the start and end date are the same, only show one date
-		return startDateString === endDateString
-			? startDateString
-			: `${startDateString} - ${endDateString}`;
-	};
+    // If the start and end date are the same, only show one date
+    return startDateString === endDateString ? startDateString : `${startDateString} - ${endDateString}`;
+  };
 
-	onMount(async () => {
-		currentAlbumName = album.albumName;
+  onMount(async () => {
+    currentAlbumName = album.albumName;
 
-		try {
-			const { data } = await api.userApi.getMyUserInfo();
-			currentUser = data;
-		} catch (e) {
-			console.log('Error [getMyUserInfo - album-viewer] ', e);
-		}
-	});
+    try {
+      const { data } = await api.userApi.getMyUserInfo();
+      currentUser = data;
+    } catch (e) {
+      console.log('Error [getMyUserInfo - album-viewer] ', e);
+    }
+  });
 
-	// Update Album Name
-	$: {
-		if (!isEditingTitle && currentAlbumName != album.albumName && isOwned) {
-			api.albumApi
-				.updateAlbumInfo({
-					id: album.id,
-					updateAlbumDto: {
-						albumName: album.albumName
-					}
-				})
-				.then(() => {
-					currentAlbumName = album.albumName;
-				})
-				.catch((e) => {
-					console.error('Error [updateAlbumInfo] ', e);
-					notificationController.show({
-						type: NotificationType.Error,
-						message: "Error updating album's name, check console for more details"
-					});
-				});
-		}
-	}
+  // Update Album Name
+  $: {
+    if (!isEditingTitle && currentAlbumName != album.albumName && isOwned) {
+      api.albumApi
+        .updateAlbumInfo({
+          id: album.id,
+          updateAlbumDto: {
+            albumName: album.albumName,
+          },
+        })
+        .then(() => {
+          currentAlbumName = album.albumName;
+        })
+        .catch((e) => {
+          console.error('Error [updateAlbumInfo] ', e);
+          notificationController.show({
+            type: NotificationType.Error,
+            message: "Error updating album's name, check console for more details",
+          });
+        });
+    }
+  }
 
-	const createAlbumHandler = async (event: CustomEvent) => {
-		const { assets }: { assets: AssetResponseDto[] } = event.detail;
-		try {
-			const { data } = await api.albumApi.addAssetsToAlbum({
-				id: album.id,
-				addAssetsDto: {
-					assetIds: assets.map((a) => a.id)
-				},
-				key: sharedLink?.key
-			});
+  const createAlbumHandler = async (event: CustomEvent) => {
+    const { assets }: { assets: AssetResponseDto[] } = event.detail;
+    try {
+      const { data } = await api.albumApi.addAssetsToAlbum({
+        id: album.id,
+        addAssetsDto: {
+          assetIds: assets.map((a) => a.id),
+        },
+        key: sharedLink?.key,
+      });
 
-			if (data.album) {
-				album = data.album;
-			}
-			isShowAssetSelection = false;
-		} catch (e) {
-			console.error('Error [createAlbumHandler] ', e);
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error creating album, check console for more details'
-			});
-		}
-	};
+      if (data.album) {
+        album = data.album;
+      }
+      isShowAssetSelection = false;
+    } catch (e) {
+      console.error('Error [createAlbumHandler] ', e);
+      notificationController.show({
+        type: NotificationType.Error,
+        message: 'Error creating album, check console for more details',
+      });
+    }
+  };
 
-	const addUserHandler = async (event: CustomEvent) => {
-		const { selectedUsers }: { selectedUsers: UserResponseDto[] } = event.detail;
+  const addUserHandler = async (event: CustomEvent) => {
+    const { selectedUsers }: { selectedUsers: UserResponseDto[] } = event.detail;
 
-		try {
-			const { data } = await api.albumApi.addUsersToAlbum({
-				id: album.id,
-				addUsersDto: {
-					sharedUserIds: Array.from(selectedUsers).map((u) => u.id)
-				}
-			});
+    try {
+      const { data } = await api.albumApi.addUsersToAlbum({
+        id: album.id,
+        addUsersDto: {
+          sharedUserIds: Array.from(selectedUsers).map((u) => u.id),
+        },
+      });
 
-			album = data;
+      album = data;
 
-			isShowShareUserSelection = false;
-		} catch (e) {
-			console.error('Error [addUserHandler] ', e);
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error adding users to album, check console for more details'
-			});
-		}
-	};
+      isShowShareUserSelection = false;
+    } catch (e) {
+      console.error('Error [addUserHandler] ', e);
+      notificationController.show({
+        type: NotificationType.Error,
+        message: 'Error adding users to album, check console for more details',
+      });
+    }
+  };
 
-	const sharedUserDeletedHandler = async (event: CustomEvent) => {
-		const { userId }: { userId: string } = event.detail;
+  const sharedUserDeletedHandler = async (event: CustomEvent) => {
+    const { userId }: { userId: string } = event.detail;
 
-		if (userId == 'me') {
-			isShowShareInfoModal = false;
-			goto(backUrl);
-			return;
-		}
+    if (userId == 'me') {
+      isShowShareInfoModal = false;
+      goto(backUrl);
+      return;
+    }
 
-		try {
-			const { data } = await api.albumApi.getAlbumInfo({ id: album.id });
+    try {
+      const { data } = await api.albumApi.getAlbumInfo({ id: album.id });
 
-			album = data;
-			isShowShareInfoModal = data.sharedUsers.length >= 1;
-		} catch (e) {
-			handleError(e, 'Error deleting share users');
-		}
-	};
+      album = data;
+      isShowShareInfoModal = data.sharedUsers.length >= 1;
+    } catch (e) {
+      handleError(e, 'Error deleting share users');
+    }
+  };
 
-	const removeAlbum = async () => {
-		try {
-			await api.albumApi.deleteAlbum({ id: album.id });
-			goto(backUrl);
-		} catch (e) {
-			console.error('Error [userDeleteMenu] ', e);
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error deleting album, check console for more details'
-			});
-		} finally {
-			isShowDeleteConfirmation = false;
-		}
-	};
+  const removeAlbum = async () => {
+    try {
+      await api.albumApi.deleteAlbum({ id: album.id });
+      goto(backUrl);
+    } catch (e) {
+      console.error('Error [userDeleteMenu] ', e);
+      notificationController.show({
+        type: NotificationType.Error,
+        message: 'Error deleting album, check console for more details',
+      });
+    } finally {
+      isShowDeleteConfirmation = false;
+    }
+  };
 
-	const downloadAlbum = async () => {
-		await downloadArchive(
-			`${album.albumName}.zip`,
-			{ albumId: album.id },
-			undefined,
-			sharedLink?.key
-		);
-	};
+  const downloadAlbum = async () => {
+    await downloadArchive(`${album.albumName}.zip`, { albumId: album.id }, undefined, sharedLink?.key);
+  };
 
-	const showAlbumOptionsMenu = ({ x, y }: MouseEvent) => {
-		contextMenuPosition = { x, y };
-		isShowAlbumOptions = !isShowAlbumOptions;
-	};
+  const showAlbumOptionsMenu = ({ x, y }: MouseEvent) => {
+    contextMenuPosition = { x, y };
+    isShowAlbumOptions = !isShowAlbumOptions;
+  };
 
-	const setAlbumThumbnailHandler = (event: CustomEvent) => {
-		const { asset }: { asset: AssetResponseDto } = event.detail;
-		try {
-			api.albumApi.updateAlbumInfo({
-				id: album.id,
-				updateAlbumDto: {
-					albumThumbnailAssetId: asset.id
-				}
-			});
-		} catch (e) {
-			console.error('Error [setAlbumThumbnailHandler] ', e);
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error setting album thumbnail, check console for more details'
-			});
-		}
+  const setAlbumThumbnailHandler = (event: CustomEvent) => {
+    const { asset }: { asset: AssetResponseDto } = event.detail;
+    try {
+      api.albumApi.updateAlbumInfo({
+        id: album.id,
+        updateAlbumDto: {
+          albumThumbnailAssetId: asset.id,
+        },
+      });
+    } catch (e) {
+      console.error('Error [setAlbumThumbnailHandler] ', e);
+      notificationController.show({
+        type: NotificationType.Error,
+        message: 'Error setting album thumbnail, check console for more details',
+      });
+    }
 
-		isShowThumbnailSelection = false;
-	};
+    isShowThumbnailSelection = false;
+  };
 
-	const onSharedLinkClickHandler = () => {
-		isShowShareUserSelection = false;
-		isShowShareLinkModal = true;
-	};
+  const onSharedLinkClickHandler = () => {
+    isShowShareUserSelection = false;
+    isShowShareLinkModal = true;
+  };
 
-	const handleSelectAll = () => {
-		multiSelectAsset = new Set(album.assets);
-	};
+  const handleSelectAll = () => {
+    multiSelectAsset = new Set(album.assets);
+  };
 </script>
 
 <section class="bg-immich-bg dark:bg-immich-dark-bg" class:hidden={isShowThumbnailSelection}>
-	<!-- Multiselection mode app bar -->
-	{#if isMultiSelectionMode}
-		<AssetSelectControlBar
-			assets={multiSelectAsset}
-			clearSelect={() => (multiSelectAsset = new Set())}
-		>
-			<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
-			{#if sharedLink?.allowDownload || !isPublicShared}
-				<DownloadAction filename="{album.albumName}.zip" sharedLinkKey={sharedLink?.key} />
-			{/if}
-			{#if isOwned}
-				<RemoveFromAlbum bind:album />
-			{/if}
-		</AssetSelectControlBar>
-	{/if}
+  <!-- Multiselection mode app bar -->
+  {#if isMultiSelectionMode}
+    <AssetSelectControlBar assets={multiSelectAsset} clearSelect={() => (multiSelectAsset = new Set())}>
+      <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
+      {#if sharedLink?.allowDownload || !isPublicShared}
+        <DownloadAction filename="{album.albumName}.zip" sharedLinkKey={sharedLink?.key} />
+      {/if}
+      {#if isOwned}
+        <RemoveFromAlbum bind:album />
+      {/if}
+    </AssetSelectControlBar>
+  {/if}
 
-	<!-- Default app bar -->
-	{#if !isMultiSelectionMode}
-		<ControlAppBar
-			on:close-button-click={() => goto(backUrl)}
-			backIcon={ArrowLeft}
-			showBackButton={(!isPublicShared && isOwned) ||
-				(!isPublicShared && !isOwned) ||
-				(isPublicShared && isOwned)}
-		>
-			<svelte:fragment slot="leading">
-				{#if isPublicShared && !isOwned}
-					<a
-						data-sveltekit-preload-data="hover"
-						class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
-						href="https://immich.app"
-					>
-						<ImmichLogo height={30} width={30} />
-						<h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">
-							IMMICH
-						</h1>
-					</a>
-				{/if}
-			</svelte:fragment>
+  <!-- Default app bar -->
+  {#if !isMultiSelectionMode}
+    <ControlAppBar
+      on:close-button-click={() => goto(backUrl)}
+      backIcon={ArrowLeft}
+      showBackButton={(!isPublicShared && isOwned) || (!isPublicShared && !isOwned) || (isPublicShared && isOwned)}
+    >
+      <svelte:fragment slot="leading">
+        {#if isPublicShared && !isOwned}
+          <a
+            data-sveltekit-preload-data="hover"
+            class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
+            href="https://immich.app"
+          >
+            <ImmichLogo height={30} width={30} />
+            <h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">IMMICH</h1>
+          </a>
+        {/if}
+      </svelte:fragment>
 
-			<svelte:fragment slot="trailing">
-				{#if !isCreatingSharedAlbum}
-					{#if !sharedLink}
-						<CircleIconButton
-							title="Add Photos"
-							on:click={() => (isShowAssetSelection = true)}
-							logo={FileImagePlusOutline}
-						/>
-					{:else if sharedLink?.allowUpload}
-						<CircleIconButton
-							title="Add Photos"
-							on:click={() => openFileUploadDialog(album.id, sharedLink?.key)}
-							logo={FileImagePlusOutline}
-						/>
-					{/if}
+      <svelte:fragment slot="trailing">
+        {#if !isCreatingSharedAlbum}
+          {#if !sharedLink}
+            <CircleIconButton
+              title="Add Photos"
+              on:click={() => (isShowAssetSelection = true)}
+              logo={FileImagePlusOutline}
+            />
+          {:else if sharedLink?.allowUpload}
+            <CircleIconButton
+              title="Add Photos"
+              on:click={() => openFileUploadDialog(album.id, sharedLink?.key)}
+              logo={FileImagePlusOutline}
+            />
+          {/if}
 
-					{#if isOwned}
-						<CircleIconButton
-							title="Share"
-							on:click={() => (isShowShareUserSelection = true)}
-							logo={ShareVariantOutline}
-						/>
-						<CircleIconButton
-							title="Remove album"
-							on:click={() => (isShowDeleteConfirmation = true)}
-							logo={DeleteOutline}
-						/>
-					{/if}
-				{/if}
+          {#if isOwned}
+            <CircleIconButton
+              title="Share"
+              on:click={() => (isShowShareUserSelection = true)}
+              logo={ShareVariantOutline}
+            />
+            <CircleIconButton
+              title="Remove album"
+              on:click={() => (isShowDeleteConfirmation = true)}
+              logo={DeleteOutline}
+            />
+          {/if}
+        {/if}
 
-				{#if album.assetCount > 0 && !isCreatingSharedAlbum}
-					{#if !isPublicShared || (isPublicShared && sharedLink?.allowDownload)}
-						<CircleIconButton
-							title="Download"
-							on:click={() => downloadAlbum()}
-							logo={FolderDownloadOutline}
-						/>
-					{/if}
+        {#if album.assetCount > 0 && !isCreatingSharedAlbum}
+          {#if !isPublicShared || (isPublicShared && sharedLink?.allowDownload)}
+            <CircleIconButton title="Download" on:click={() => downloadAlbum()} logo={FolderDownloadOutline} />
+          {/if}
 
-					{#if !isPublicShared && isOwned}
-						<CircleIconButton
-							title="Album options"
-							on:click={showAlbumOptionsMenu}
-							logo={DotsVertical}
-						>
-							{#if isShowAlbumOptions}
-								<ContextMenu
-									{...contextMenuPosition}
-									on:outclick={() => (isShowAlbumOptions = false)}
-								>
-									<MenuOption
-										on:click={() => {
-											isShowThumbnailSelection = true;
-											isShowAlbumOptions = false;
-										}}
-										text="Set album cover"
-									/>
-								</ContextMenu>
-							{/if}
-						</CircleIconButton>
-					{/if}
-				{/if}
+          {#if !isPublicShared && isOwned}
+            <CircleIconButton title="Album options" on:click={showAlbumOptionsMenu} logo={DotsVertical}>
+              {#if isShowAlbumOptions}
+                <ContextMenu {...contextMenuPosition} on:outclick={() => (isShowAlbumOptions = false)}>
+                  <MenuOption
+                    on:click={() => {
+                      isShowThumbnailSelection = true;
+                      isShowAlbumOptions = false;
+                    }}
+                    text="Set album cover"
+                  />
+                </ContextMenu>
+              {/if}
+            </CircleIconButton>
+          {/if}
+        {/if}
 
-				{#if isPublicShared}
-					<ThemeButton />
-				{/if}
+        {#if isPublicShared}
+          <ThemeButton />
+        {/if}
 
-				{#if isCreatingSharedAlbum && album.sharedUsers.length == 0}
-					<Button
-						size="sm"
-						rounded="lg"
-						disabled={album.assetCount == 0}
-						on:click={() => (isShowShareUserSelection = true)}
-					>
-						Share
-					</Button>
-				{/if}
-			</svelte:fragment>
-		</ControlAppBar>
-	{/if}
+        {#if isCreatingSharedAlbum && album.sharedUsers.length == 0}
+          <Button
+            size="sm"
+            rounded="lg"
+            disabled={album.assetCount == 0}
+            on:click={() => (isShowShareUserSelection = true)}
+          >
+            Share
+          </Button>
+        {/if}
+      </svelte:fragment>
+    </ControlAppBar>
+  {/if}
 
-	<section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
-		<input
-			on:keydown={(e) => {
-				if (e.key == 'Enter') {
-					isEditingTitle = false;
-					titleInput.blur();
-				}
-			}}
-			on:focus={() => (isEditingTitle = true)}
-			on:blur={() => (isEditingTitle = false)}
-			class={`transition-all text-6xl text-immich-primary dark:text-immich-dark-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 dark:focus:border-immich-dark-primary bg-immich-bg dark:bg-immich-dark-bg dark:focus:bg-immich-dark-gray`}
-			type="text"
-			bind:value={album.albumName}
-			disabled={!isOwned}
-			bind:this={titleInput}
-		/>
+  <section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
+    <input
+      on:keydown={(e) => {
+        if (e.key == 'Enter') {
+          isEditingTitle = false;
+          titleInput.blur();
+        }
+      }}
+      on:focus={() => (isEditingTitle = true)}
+      on:blur={() => (isEditingTitle = false)}
+      class={`transition-all text-6xl text-immich-primary dark:text-immich-dark-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 dark:focus:border-immich-dark-primary bg-immich-bg dark:bg-immich-dark-bg dark:focus:bg-immich-dark-gray`}
+      type="text"
+      bind:value={album.albumName}
+      disabled={!isOwned}
+      bind:this={titleInput}
+    />
 
-		{#if album.assetCount > 0}
-			<span class="flex gap-2 my-4 text-sm text-gray-500 font-medium" data-testid="album-details">
-				<p class="">{getDateRange()}</p>
-				<p>·</p>
-				<p>{album.assetCount} items</p>
-			</span>
-		{/if}
-		{#if album.shared}
-			<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}
+    {#if album.assetCount > 0}
+      <span class="flex gap-2 my-4 text-sm text-gray-500 font-medium" data-testid="album-details">
+        <p class="">{getDateRange()}</p>
+        <p>·</p>
+        <p>{album.assetCount} items</p>
+      </span>
+    {/if}
+    {#if album.shared}
+      <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}
 
-				<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}
+        <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}
 
-		{#if album.assetCount > 0}
-			<GalleryViewer
-				assets={album.assets}
-				{sharedLink}
-				bind:selectedAssets={multiSelectAsset}
-				viewFrom="album-page"
-			/>
-		{: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 dark:text-immich-dark-fg">ADD PHOTOS</p>
-					<button
-						on:click={() => (isShowAssetSelection = true)}
-						class="w-full py-8 border bg-immich-bg dark:bg-immich-dark-gray text-immich-fg dark:text-immich-dark-fg dark:hover:text-immich-dark-primary rounded-md mt-5 flex place-items-center gap-6 px-8 transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none"
-					>
-						<span class="text-text-immich-primary dark:text-immich-dark-primary"
-							><Plus size="24" />
-						</span>
-						<span class="text-lg">Select photos</span>
-					</button>
-				</div>
-			</section>
-		{/if}
-	</section>
+    {#if album.assetCount > 0}
+      <GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} viewFrom="album-page" />
+    {: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 dark:text-immich-dark-fg">ADD PHOTOS</p>
+          <button
+            on:click={() => (isShowAssetSelection = true)}
+            class="w-full py-8 border bg-immich-bg dark:bg-immich-dark-gray text-immich-fg dark:text-immich-dark-fg dark:hover:text-immich-dark-primary rounded-md mt-5 flex place-items-center gap-6 px-8 transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none"
+          >
+            <span class="text-text-immich-primary dark:text-immich-dark-primary"><Plus size="24" /> </span>
+            <span class="text-lg">Select photos</span>
+          </button>
+        </div>
+      </section>
+    {/if}
+  </section>
 </section>
 
 {#if isShowAssetSelection}
-	<AssetSelection
-		albumId={album.id}
-		assetsInAlbum={album.assets}
-		on:go-back={() => (isShowAssetSelection = false)}
-		on:create-album={createAlbumHandler}
-	/>
+  <AssetSelection
+    albumId={album.id}
+    assetsInAlbum={album.assets}
+    on:go-back={() => (isShowAssetSelection = false)}
+    on:create-album={createAlbumHandler}
+  />
 {/if}
 
 {#if isShowShareUserSelection}
-	<UserSelectionModal
-		{album}
-		on:close={() => (isShowShareUserSelection = false)}
-		on:add-user={addUserHandler}
-		on:sharedlinkclick={onSharedLinkClickHandler}
-		sharedUsersInAlbum={new Set(album.sharedUsers)}
-	/>
+  <UserSelectionModal
+    {album}
+    on:close={() => (isShowShareUserSelection = false)}
+    on:add-user={addUserHandler}
+    on:sharedlinkclick={onSharedLinkClickHandler}
+    sharedUsersInAlbum={new Set(album.sharedUsers)}
+  />
 {/if}
 
 {#if isShowShareLinkModal}
-	<CreateSharedLinkModal
-		on:close={() => (isShowShareLinkModal = false)}
-		shareType={SharedLinkType.Album}
-		{album}
-	/>
+  <CreateSharedLinkModal on:close={() => (isShowShareLinkModal = false)} shareType={SharedLinkType.Album} {album} />
 {/if}
 {#if isShowShareInfoModal}
-	<ShareInfoModal
-		on:close={() => (isShowShareInfoModal = false)}
-		{album}
-		on:user-deleted={sharedUserDeletedHandler}
-	/>
+  <ShareInfoModal on:close={() => (isShowShareInfoModal = false)} {album} on:user-deleted={sharedUserDeletedHandler} />
 {/if}
 
 {#if isShowThumbnailSelection}
-	<ThumbnailSelection
-		{album}
-		on:close={() => (isShowThumbnailSelection = false)}
-		on:thumbnail-selected={setAlbumThumbnailHandler}
-	/>
+  <ThumbnailSelection
+    {album}
+    on:close={() => (isShowThumbnailSelection = false)}
+    on:thumbnail-selected={setAlbumThumbnailHandler}
+  />
 {/if}
 
 {#if isShowDeleteConfirmation}
-	<ConfirmDialogue
-		title="Delete Album"
-		confirmText="Delete"
-		on:confirm={removeAlbum}
-		on:cancel={() => (isShowDeleteConfirmation = false)}
-	>
-		<svelte:fragment slot="prompt">
-			<p>Are you sure you want to delete the album <b>{album.albumName}</b>?</p>
-			<p>If this album is shared, other users will not be able to access it anymore.</p>
-		</svelte:fragment>
-	</ConfirmDialogue>
+  <ConfirmDialogue
+    title="Delete Album"
+    confirmText="Delete"
+    on:confirm={removeAlbum}
+    on:cancel={() => (isShowDeleteConfirmation = false)}
+  >
+    <svelte:fragment slot="prompt">
+      <p>Are you sure you want to delete the album <b>{album.albumName}</b>?</p>
+      <p>If this album is shared, other users will not be able to access it anymore.</p>
+    </svelte:fragment>
+  </ConfirmDialogue>
 {/if}
diff --git a/web/src/lib/components/album-page/asset-selection.svelte b/web/src/lib/components/album-page/asset-selection.svelte
index 6282da2c30..054ed7c4ca 100644
--- a/web/src/lib/components/album-page/asset-selection.svelte
+++ b/web/src/lib/components/album-page/asset-selection.svelte
@@ -1,80 +1,69 @@
 <script lang="ts">
-	import {
-		assetInteractionStore,
-		assetsInAlbumStoreState,
-		selectedAssets
-	} from '$lib/stores/asset-interaction.store';
-	import { locale } from '$lib/stores/preferences.store';
-	import { openFileUploadDialog } from '$lib/utils/file-uploader';
-	import type { AssetResponseDto } from '@api';
-	import { createEventDispatcher, onMount } from 'svelte';
-	import { quintOut } from 'svelte/easing';
-	import { fly } from 'svelte/transition';
-	import Button from '../elements/buttons/button.svelte';
-	import AssetGrid from '../photos-page/asset-grid.svelte';
-	import ControlAppBar from '../shared-components/control-app-bar.svelte';
+  import { assetInteractionStore, assetsInAlbumStoreState, selectedAssets } from '$lib/stores/asset-interaction.store';
+  import { locale } from '$lib/stores/preferences.store';
+  import { openFileUploadDialog } from '$lib/utils/file-uploader';
+  import type { AssetResponseDto } from '@api';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import { quintOut } from 'svelte/easing';
+  import { fly } from 'svelte/transition';
+  import Button from '../elements/buttons/button.svelte';
+  import AssetGrid from '../photos-page/asset-grid.svelte';
+  import ControlAppBar from '../shared-components/control-app-bar.svelte';
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	export let albumId: string;
-	export let assetsInAlbum: AssetResponseDto[];
+  export let albumId: string;
+  export let assetsInAlbum: AssetResponseDto[];
 
-	onMount(() => {
-		$assetsInAlbumStoreState = assetsInAlbum;
-	});
+  onMount(() => {
+    $assetsInAlbumStoreState = assetsInAlbum;
+  });
 
-	const addSelectedAssets = async () => {
-		dispatch('create-album', {
-			assets: Array.from($selectedAssets)
-		});
+  const addSelectedAssets = async () => {
+    dispatch('create-album', {
+      assets: Array.from($selectedAssets),
+    });
 
-		assetInteractionStore.clearMultiselect();
-	};
-	const handleSelectFromComputerClicked = async () => {
-		await openFileUploadDialog(albumId, '');
-		assetInteractionStore.clearMultiselect();
-		dispatch('go-back');
-	};
+    assetInteractionStore.clearMultiselect();
+  };
+  const handleSelectFromComputerClicked = async () => {
+    await openFileUploadDialog(albumId, '');
+    assetInteractionStore.clearMultiselect();
+    dispatch('go-back');
+  };
 </script>
 
 <section
-	transition:fly={{ y: 500, duration: 100, easing: quintOut }}
-	class="absolute top-0 left-0 w-full h-full bg-immich-bg dark:bg-immich-dark-bg z-[9999]"
+  transition:fly={{ y: 500, duration: 100, easing: quintOut }}
+  class="absolute top-0 left-0 w-full h-full bg-immich-bg dark:bg-immich-dark-bg z-[9999]"
 >
-	<ControlAppBar
-		on:close-button-click={() => {
-			assetInteractionStore.clearMultiselect();
-			dispatch('go-back');
-		}}
-	>
-		<svelte:fragment slot="leading">
-			{#if $selectedAssets.size == 0}
-				<p class="text-lg dark:text-immich-dark-fg">Add to album</p>
-			{:else}
-				<p class="text-lg dark:text-immich-dark-fg">
-					{$selectedAssets.size.toLocaleString($locale)} selected
-				</p>
-			{/if}
-		</svelte:fragment>
+  <ControlAppBar
+    on:close-button-click={() => {
+      assetInteractionStore.clearMultiselect();
+      dispatch('go-back');
+    }}
+  >
+    <svelte:fragment slot="leading">
+      {#if $selectedAssets.size == 0}
+        <p class="text-lg dark:text-immich-dark-fg">Add to album</p>
+      {:else}
+        <p class="text-lg dark:text-immich-dark-fg">
+          {$selectedAssets.size.toLocaleString($locale)} selected
+        </p>
+      {/if}
+    </svelte:fragment>
 
-		<svelte:fragment slot="trailing">
-			<button
-				on:click={handleSelectFromComputerClicked}
-				class="text-immich-primary dark:text-immich-dark-primary text-sm hover:bg-immich-primary/10 dark:hover:bg-immich-dark-primary/25 transition-all px-6 py-2 rounded-lg font-medium"
-			>
-				Select from computer
-			</button>
-			<Button
-				size="sm"
-				rounded="lg"
-				disabled={$selectedAssets.size === 0}
-				on:click={addSelectedAssets}
-			>
-				Done
-			</Button>
-		</svelte:fragment>
-	</ControlAppBar>
-	<section class="pt-[100px] pl-[70px] grid h-screen bg-immich-bg dark:bg-immich-dark-bg">
-		<AssetGrid isAlbumSelectionMode={true} />
-	</section>
+    <svelte:fragment slot="trailing">
+      <button
+        on:click={handleSelectFromComputerClicked}
+        class="text-immich-primary dark:text-immich-dark-primary text-sm hover:bg-immich-primary/10 dark:hover:bg-immich-dark-primary/25 transition-all px-6 py-2 rounded-lg font-medium"
+      >
+        Select from computer
+      </button>
+      <Button size="sm" rounded="lg" disabled={$selectedAssets.size === 0} on:click={addSelectedAssets}>Done</Button>
+    </svelte:fragment>
+  </ControlAppBar>
+  <section class="pt-[100px] pl-[70px] grid h-screen bg-immich-bg dark:bg-immich-dark-bg">
+    <AssetGrid isAlbumSelectionMode={true} />
+  </section>
 </section>
diff --git a/web/src/lib/components/album-page/share-info-modal.svelte b/web/src/lib/components/album-page/share-info-modal.svelte
index 593e93b345..f93bd33936 100644
--- a/web/src/lib/components/album-page/share-info-modal.svelte
+++ b/web/src/lib/components/album-page/share-info-modal.svelte
@@ -1,144 +1,140 @@
 <script lang="ts">
-	import { createEventDispatcher, onMount } from 'svelte';
-	import { AlbumResponseDto, api, UserResponseDto } from '@api';
-	import BaseModal from '../shared-components/base-modal.svelte';
-	import UserAvatar from '../shared-components/user-avatar.svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
-	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import { handleError } from '../../utils/handle-error';
-	import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import { AlbumResponseDto, api, UserResponseDto } from '@api';
+  import BaseModal from '../shared-components/base-modal.svelte';
+  import UserAvatar from '../shared-components/user-avatar.svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
+  import MenuOption from '../shared-components/context-menu/menu-option.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import { handleError } from '../../utils/handle-error';
+  import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
 
-	export let album: AlbumResponseDto;
+  export let album: AlbumResponseDto;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	let currentUser: UserResponseDto;
-	let position = { x: 0, y: 0 };
-	let selectedMenuUser: UserResponseDto | null = null;
-	let selectedRemoveUser: UserResponseDto | null = null;
+  let currentUser: UserResponseDto;
+  let position = { x: 0, y: 0 };
+  let selectedMenuUser: UserResponseDto | null = null;
+  let selectedRemoveUser: UserResponseDto | null = null;
 
-	$: isOwned = currentUser?.id == album.ownerId;
+  $: isOwned = currentUser?.id == album.ownerId;
 
-	onMount(async () => {
-		try {
-			const { data } = await api.userApi.getMyUserInfo();
-			currentUser = data;
-		} catch (e) {
-			handleError(e, 'Unable to refresh user');
-		}
-	});
+  onMount(async () => {
+    try {
+      const { data } = await api.userApi.getMyUserInfo();
+      currentUser = data;
+    } catch (e) {
+      handleError(e, 'Unable to refresh user');
+    }
+  });
 
-	const showContextMenu = (user: UserResponseDto) => {
-		const iconButton = document.getElementById('icon-' + user.id);
+  const showContextMenu = (user: UserResponseDto) => {
+    const iconButton = document.getElementById('icon-' + user.id);
 
-		if (iconButton) {
-			position = {
-				x: iconButton.getBoundingClientRect().left,
-				y: iconButton.getBoundingClientRect().bottom
-			};
-		}
+    if (iconButton) {
+      position = {
+        x: iconButton.getBoundingClientRect().left,
+        y: iconButton.getBoundingClientRect().bottom,
+      };
+    }
 
-		selectedMenuUser = user;
-		selectedRemoveUser = null;
-	};
+    selectedMenuUser = user;
+    selectedRemoveUser = null;
+  };
 
-	const handleMenuRemove = () => {
-		selectedRemoveUser = selectedMenuUser;
-		selectedMenuUser = null;
-	};
+  const handleMenuRemove = () => {
+    selectedRemoveUser = selectedMenuUser;
+    selectedMenuUser = null;
+  };
 
-	const handleRemoveUser = async () => {
-		if (!selectedRemoveUser) {
-			return;
-		}
+  const handleRemoveUser = async () => {
+    if (!selectedRemoveUser) {
+      return;
+    }
 
-		const userId = selectedRemoveUser.id === currentUser?.id ? 'me' : selectedRemoveUser.id;
+    const userId = selectedRemoveUser.id === currentUser?.id ? 'me' : selectedRemoveUser.id;
 
-		try {
-			await api.albumApi.removeUserFromAlbum({ id: album.id, userId });
-			dispatch('user-deleted', { userId });
-			const message =
-				userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.firstName}`;
-			notificationController.show({ type: NotificationType.Info, message });
-		} catch (e) {
-			handleError(e, 'Unable to remove user');
-		} finally {
-			selectedRemoveUser = null;
-		}
-	};
+    try {
+      await api.albumApi.removeUserFromAlbum({ id: album.id, userId });
+      dispatch('user-deleted', { userId });
+      const message = userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.firstName}`;
+      notificationController.show({ type: NotificationType.Info, message });
+    } catch (e) {
+      handleError(e, 'Unable to remove user');
+    } finally {
+      selectedRemoveUser = null;
+    }
+  };
 </script>
 
 {#if !selectedRemoveUser}
-	<BaseModal on:close={() => dispatch('close')}>
-		<svelte:fragment slot="title">
-			<span class="flex gap-2 place-items-center">
-				<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Options</p>
-			</span>
-		</svelte:fragment>
+  <BaseModal on:close={() => dispatch('close')}>
+    <svelte:fragment slot="title">
+      <span class="flex gap-2 place-items-center">
+        <p class="font-medium text-immich-fg dark:text-immich-dark-fg">Options</p>
+      </span>
+    </svelte:fragment>
 
-		<section class="max-h-[400px] overflow-y-auto immich-scrollbar pb-4">
-			{#each album.sharedUsers as user}
-				<div
-					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">
-						<UserAvatar {user} size="md" autoColor />
-						<p class="font-medium text-sm">{user.firstName} {user.lastName}</p>
-					</div>
+    <section class="max-h-[400px] overflow-y-auto immich-scrollbar pb-4">
+      {#each album.sharedUsers as user}
+        <div
+          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">
+            <UserAvatar {user} size="md" autoColor />
+            <p class="font-medium text-sm">{user.firstName} {user.lastName}</p>
+          </div>
 
-					<div id={`icon-${user.id}`} class="flex place-items-center">
-						{#if isOwned}
-							<div>
-								<CircleIconButton
-									on:click={() => showContextMenu(user)}
-									logo={DotsVertical}
-									backgroundColor="transparent"
-									hoverColor="#e2e7e9"
-									size="20"
-								/>
+          <div id={`icon-${user.id}`} class="flex place-items-center">
+            {#if isOwned}
+              <div>
+                <CircleIconButton
+                  on:click={() => showContextMenu(user)}
+                  logo={DotsVertical}
+                  backgroundColor="transparent"
+                  hoverColor="#e2e7e9"
+                  size="20"
+                />
 
-								{#if selectedMenuUser === user}
-									<ContextMenu {...position} on:outclick={() => (selectedMenuUser = null)}>
-										<MenuOption on:click={handleMenuRemove} text="Remove" />
-									</ContextMenu>
-								{/if}
-							</div>
-						{:else if user.id == currentUser?.id}
-							<button
-								on:click={() => (selectedRemoveUser = user)}
-								class="text-sm text-immich-primary dark:text-immich-dark-primary font-medium transition-colors hover:text-immich-primary/75"
-								>Leave</button
-							>
-						{/if}
-					</div>
-				</div>
-			{/each}
-		</section>
-	</BaseModal>
+                {#if selectedMenuUser === user}
+                  <ContextMenu {...position} on:outclick={() => (selectedMenuUser = null)}>
+                    <MenuOption on:click={handleMenuRemove} text="Remove" />
+                  </ContextMenu>
+                {/if}
+              </div>
+            {:else if user.id == currentUser?.id}
+              <button
+                on:click={() => (selectedRemoveUser = user)}
+                class="text-sm text-immich-primary dark:text-immich-dark-primary font-medium transition-colors hover:text-immich-primary/75"
+                >Leave</button
+              >
+            {/if}
+          </div>
+        </div>
+      {/each}
+    </section>
+  </BaseModal>
 {/if}
 
 {#if selectedRemoveUser && selectedRemoveUser?.id === currentUser?.id}
-	<ConfirmDialogue
-		title="Leave Album?"
-		prompt="Are you sure you want to leave {album.albumName}?"
-		confirmText="Leave"
-		on:confirm={handleRemoveUser}
-		on:cancel={() => (selectedRemoveUser = null)}
-	/>
+  <ConfirmDialogue
+    title="Leave Album?"
+    prompt="Are you sure you want to leave {album.albumName}?"
+    confirmText="Leave"
+    on:confirm={handleRemoveUser}
+    on:cancel={() => (selectedRemoveUser = null)}
+  />
 {/if}
 
 {#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id}
-	<ConfirmDialogue
-		title="Remove User?"
-		prompt="Are you sure you want to remove {selectedRemoveUser.firstName} {selectedRemoveUser.lastName}"
-		confirmText="Remove"
-		on:confirm={handleRemoveUser}
-		on:cancel={() => (selectedRemoveUser = null)}
-	/>
+  <ConfirmDialogue
+    title="Remove User?"
+    prompt="Are you sure you want to remove {selectedRemoveUser.firstName} {selectedRemoveUser.lastName}"
+    confirmText="Remove"
+    on:confirm={handleRemoveUser}
+    on:cancel={() => (selectedRemoveUser = null)}
+  />
 {/if}
diff --git a/web/src/lib/components/album-page/thumbnail-selection.svelte b/web/src/lib/components/album-page/thumbnail-selection.svelte
index 6486ad60e4..0d9d5deb35 100644
--- a/web/src/lib/components/album-page/thumbnail-selection.svelte
+++ b/web/src/lib/components/album-page/thumbnail-selection.svelte
@@ -1,57 +1,53 @@
 <script lang="ts">
-	import type { AlbumResponseDto, AssetResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import { quintOut } from 'svelte/easing';
-	import { fly } from 'svelte/transition';
-	import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import ControlAppBar from '../shared-components/control-app-bar.svelte';
+  import type { AlbumResponseDto, AssetResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import { quintOut } from 'svelte/easing';
+  import { fly } from 'svelte/transition';
+  import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import ControlAppBar from '../shared-components/control-app-bar.svelte';
 
-	export let album: AlbumResponseDto;
+  export let album: AlbumResponseDto;
 
-	let selectedThumbnail: AssetResponseDto | undefined;
-	const dispatch = createEventDispatcher();
+  let selectedThumbnail: AssetResponseDto | undefined;
+  const dispatch = createEventDispatcher();
 
-	$: isSelected = (id: string): boolean | undefined => {
-		if (!selectedThumbnail && album.albumThumbnailAssetId == id) {
-			return true;
-		} else {
-			return selectedThumbnail?.id == id;
-		}
-	};
+  $: isSelected = (id: string): boolean | undefined => {
+    if (!selectedThumbnail && album.albumThumbnailAssetId == id) {
+      return true;
+    } else {
+      return selectedThumbnail?.id == id;
+    }
+  };
 </script>
 
 <section
-	transition:fly={{ y: 500, duration: 100, easing: quintOut }}
-	class="absolute top-0 left-0 w-full h-full py-[160px] bg-immich-bg dark:bg-immich-dark-bg z-[9999]"
+  transition:fly={{ y: 500, duration: 100, easing: quintOut }}
+  class="absolute top-0 left-0 w-full h-full py-[160px] bg-immich-bg dark:bg-immich-dark-bg z-[9999]"
 >
-	<ControlAppBar on:close-button-click={() => dispatch('close')}>
-		<svelte:fragment slot="leading">
-			<p class="text-lg">Select album cover</p>
-		</svelte:fragment>
+  <ControlAppBar on:close-button-click={() => dispatch('close')}>
+    <svelte:fragment slot="leading">
+      <p class="text-lg">Select album cover</p>
+    </svelte:fragment>
 
-		<svelte:fragment slot="trailing">
-			<Button
-				size="sm"
-				rounded="lg"
-				disabled={selectedThumbnail == undefined}
-				on:click={() => dispatch('thumbnail-selected', { asset: selectedThumbnail })}
-			>
-				Done
-			</Button>
-		</svelte:fragment>
-	</ControlAppBar>
+    <svelte:fragment slot="trailing">
+      <Button
+        size="sm"
+        rounded="lg"
+        disabled={selectedThumbnail == undefined}
+        on:click={() => dispatch('thumbnail-selected', { asset: selectedThumbnail })}
+      >
+        Done
+      </Button>
+    </svelte:fragment>
+  </ControlAppBar>
 
-	<section class="flex flex-wrap gap-14 px-20 overflow-y-auto">
-		<!-- Image grid -->
-		<div class="flex flex-wrap gap-[2px]">
-			{#each album.assets as asset}
-				<Thumbnail
-					{asset}
-					on:click={() => (selectedThumbnail = asset)}
-					selected={isSelected(asset.id)}
-				/>
-			{/each}
-		</div>
-	</section>
+  <section class="flex flex-wrap gap-14 px-20 overflow-y-auto">
+    <!-- Image grid -->
+    <div class="flex flex-wrap gap-[2px]">
+      {#each album.assets as asset}
+        <Thumbnail {asset} on:click={() => (selectedThumbnail = asset)} selected={isSelected(asset.id)} />
+      {/each}
+    </div>
+  </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
index 207b9e440f..3312dbd1d9 100644
--- a/web/src/lib/components/album-page/user-selection-modal.svelte
+++ b/web/src/lib/components/album-page/user-selection-modal.svelte
@@ -1,149 +1,146 @@
 <script lang="ts">
-	import { createEventDispatcher, onMount } from 'svelte';
-	import { AlbumResponseDto, api, SharedLinkResponseDto, UserResponseDto } from '@api';
-	import BaseModal from '../shared-components/base-modal.svelte';
-	import UserAvatar from '../shared-components/user-avatar.svelte';
-	import Link from 'svelte-material-icons/Link.svelte';
-	import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
-	import { goto } from '$app/navigation';
-	import ImmichLogo from '../shared-components/immich-logo.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import { AppRoute } from '$lib/constants';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import { AlbumResponseDto, api, SharedLinkResponseDto, UserResponseDto } from '@api';
+  import BaseModal from '../shared-components/base-modal.svelte';
+  import UserAvatar from '../shared-components/user-avatar.svelte';
+  import Link from 'svelte-material-icons/Link.svelte';
+  import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
+  import { goto } from '$app/navigation';
+  import ImmichLogo from '../shared-components/immich-logo.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import { AppRoute } from '$lib/constants';
 
-	export let album: AlbumResponseDto;
-	export let sharedUsersInAlbum: Set<UserResponseDto>;
-	let users: UserResponseDto[] = [];
-	let selectedUsers: UserResponseDto[] = [];
+  export let album: AlbumResponseDto;
+  export let sharedUsersInAlbum: Set<UserResponseDto>;
+  let users: UserResponseDto[] = [];
+  let selectedUsers: UserResponseDto[] = [];
 
-	const dispatch = createEventDispatcher();
-	let sharedLinks: SharedLinkResponseDto[] = [];
-	onMount(async () => {
-		await getSharedLinks();
-		const { data } = await api.userApi.getAllUsers({ isAll: false });
+  const dispatch = createEventDispatcher();
+  let sharedLinks: SharedLinkResponseDto[] = [];
+  onMount(async () => {
+    await getSharedLinks();
+    const { data } = await api.userApi.getAllUsers({ isAll: false });
 
-		// remove invalid users
-		users = data.filter((user) => !(user.deletedAt || user.id === album.ownerId));
+    // remove invalid users
+    users = data.filter((user) => !(user.deletedAt || user.id === album.ownerId));
 
-		// Remove the existed shared users from the album
-		sharedUsersInAlbum.forEach((sharedUser) => {
-			users = users.filter((user) => user.id !== sharedUser.id);
-		});
-	});
+    // Remove the existed shared users from the album
+    sharedUsersInAlbum.forEach((sharedUser) => {
+      users = users.filter((user) => user.id !== sharedUser.id);
+    });
+  });
 
-	const getSharedLinks = async () => {
-		const { data } = await api.sharedLinkApi.getAllSharedLinks();
+  const getSharedLinks = async () => {
+    const { data } = await api.sharedLinkApi.getAllSharedLinks();
 
-		sharedLinks = data.filter((link) => link.album?.id === album.id);
-	};
+    sharedLinks = data.filter((link) => link.album?.id === album.id);
+  };
 
-	const selectUser = (user: UserResponseDto) => {
-		if (selectedUsers.includes(user)) {
-			selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
-		} else {
-			selectedUsers = [...selectedUsers, user];
-		}
-	};
+  const selectUser = (user: UserResponseDto) => {
+    if (selectedUsers.includes(user)) {
+      selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
+    } else {
+      selectedUsers = [...selectedUsers, user];
+    }
+  };
 
-	const deselectUser = (user: UserResponseDto) => {
-		selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
-	};
+  const deselectUser = (user: UserResponseDto) => {
+    selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
+  };
 
-	const onSharedLinkClick = () => {
-		dispatch('sharedlinkclick');
-	};
+  const onSharedLinkClick = () => {
+    dispatch('sharedlinkclick');
+  };
 </script>
 
 <BaseModal on:close={() => dispatch('close')}>
-	<svelte:fragment slot="title">
-		<span class="flex gap-2 place-items-center">
-			<ImmichLogo width={24} />
-			<p class="font-medium">Invite to album</p>
-		</span>
-	</svelte:fragment>
+  <svelte:fragment slot="title">
+    <span class="flex gap-2 place-items-center">
+      <ImmichLogo width={24} />
+      <p class="font-medium">Invite to album</p>
+    </span>
+  </svelte:fragment>
 
-	<div class="max-h-[300px] overflow-y-auto immich-scrollbar">
-		{#if selectedUsers.length > 0}
-			<div class="flex gap-4 py-2 px-5 overflow-x-auto place-items-center mb-2">
-				<p class="font-medium">To</p>
+  <div class="max-h-[300px] overflow-y-auto immich-scrollbar">
+    {#if selectedUsers.length > 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 selectedUsers as user}
-					{#key user.id}
-						<button
-							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"
-						>
-							<UserAvatar {user} size="sm" autoColor />
-							<p class="text-xs font-medium">{user.firstName} {user.lastName}</p>
-						</button>
-					{/key}
-				{/each}
-			</div>
-		{/if}
+        {#each selectedUsers as user}
+          {#key user.id}
+            <button
+              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"
+            >
+              <UserAvatar {user} size="sm" autoColor />
+              <p class="text-xs font-medium">{user.firstName} {user.lastName}</p>
+            </button>
+          {/key}
+        {/each}
+      </div>
+    {/if}
 
-		{#if users.length > 0}
-			<p class="text-xs font-medium px-5">SUGGESTIONS</p>
+    {#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 dark:hover:bg-gray-700 transition-all"
-					>
-						{#if selectedUsers.includes(user)}
-							<span
-								class="bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-bg rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl dark:border-immich-dark-gray"
-								>✓</span
-							>
-						{:else}
-							<UserAvatar {user} size="md" autoColor />
-						{/if}
+      <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 dark:hover:bg-gray-700 transition-all"
+          >
+            {#if selectedUsers.includes(user)}
+              <span
+                class="bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-bg rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl dark:border-immich-dark-gray"
+                >✓</span
+              >
+            {:else}
+              <UserAvatar {user} size="md" autoColor />
+            {/if}
 
-						<div class="text-left">
-							<p class="text-immich-fg dark:text-immich-dark-fg">
-								{user.firstName}
-								{user.lastName}
-							</p>
-							<p class="text-xs">
-								{user.email}
-							</p>
-						</div>
-					</button>
-				{/each}
-			</div>
-		{:else}
-			<p class="text-sm p-5">
-				Looks like you have shared this album with all users or you don't have any user to share
-				with.
-			</p>
-		{/if}
+            <div class="text-left">
+              <p class="text-immich-fg dark:text-immich-dark-fg">
+                {user.firstName}
+                {user.lastName}
+              </p>
+              <p class="text-xs">
+                {user.email}
+              </p>
+            </div>
+          </button>
+        {/each}
+      </div>
+    {:else}
+      <p class="text-sm p-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.length > 0}
-			<div class="flex place-content-end p-5">
-				<Button size="sm" rounded="lg" on:click={() => dispatch('add-user', { selectedUsers })}>
-					Add
-				</Button>
-			</div>
-		{/if}
-	</div>
+    {#if selectedUsers.length > 0}
+      <div class="flex place-content-end p-5">
+        <Button size="sm" rounded="lg" on:click={() => dispatch('add-user', { selectedUsers })}>Add</Button>
+      </div>
+    {/if}
+  </div>
 
-	<hr />
-	<div id="shared-buttons" class="flex my-4 justify-around place-items-center place-content-center">
-		<button
-			class="flex flex-col gap-2 place-items-center place-content-center hover:cursor-pointer"
-			on:click={onSharedLinkClick}
-		>
-			<Link size={24} />
-			<p class="text-sm">Create link</p>
-		</button>
+  <hr />
+  <div id="shared-buttons" class="flex my-4 justify-around place-items-center place-content-center">
+    <button
+      class="flex flex-col gap-2 place-items-center place-content-center hover:cursor-pointer"
+      on:click={onSharedLinkClick}
+    >
+      <Link size={24} />
+      <p class="text-sm">Create link</p>
+    </button>
 
-		{#if sharedLinks.length}
-			<button
-				class="flex flex-col gap-2 place-items-center place-content-center hover:cursor-pointer"
-				on:click={() => goto(AppRoute.SHARED_LINKS)}
-			>
-				<ShareCircle size={24} />
-				<p class="text-sm">View links</p>
-			</button>
-		{/if}
-	</div>
+    {#if sharedLinks.length}
+      <button
+        class="flex flex-col gap-2 place-items-center place-content-center hover:cursor-pointer"
+        on:click={() => goto(AppRoute.SHARED_LINKS)}
+      >
+        <ShareCircle size={24} />
+        <p class="text-sm">View links</p>
+      </button>
+    {/if}
+  </div>
 </BaseModal>
diff --git a/web/src/lib/components/asset-viewer/album-list-item.svelte b/web/src/lib/components/asset-viewer/album-list-item.svelte
index dab58f24fd..2e8781732a 100644
--- a/web/src/lib/components/asset-viewer/album-list-item.svelte
+++ b/web/src/lib/components/asset-viewer/album-list-item.svelte
@@ -1,56 +1,56 @@
 <script lang="ts">
-	import { AlbumResponseDto, ThumbnailFormat, api } from '@api';
-	import { createEventDispatcher } from 'svelte';
+  import { AlbumResponseDto, ThumbnailFormat, api } from '@api';
+  import { createEventDispatcher } from 'svelte';
 
-	const dispatcher = createEventDispatcher();
+  const dispatcher = createEventDispatcher();
 
-	export let album: AlbumResponseDto;
-	export let variant: 'simple' | 'full' = 'full';
-	export let searchQuery = '';
-	let albumNameArray: string[] = ['', '', ''];
+  export let album: AlbumResponseDto;
+  export let variant: 'simple' | 'full' = 'full';
+  export let searchQuery = '';
+  let albumNameArray: string[] = ['', '', ''];
 
-	// This part of the code is responsible for splitting album name into 3 parts where part 2 is the search query
-	// It is used to highlight the search query in the album name
-	$: {
-		let { albumName } = album;
-		let findIndex = albumName.toLowerCase().indexOf(searchQuery.toLowerCase());
-		let findLength = searchQuery.length;
-		albumNameArray = [
-			albumName.slice(0, findIndex),
-			albumName.slice(findIndex, findIndex + findLength),
-			albumName.slice(findIndex + findLength)
-		];
-	}
+  // This part of the code is responsible for splitting album name into 3 parts where part 2 is the search query
+  // It is used to highlight the search query in the album name
+  $: {
+    let { albumName } = album;
+    let findIndex = albumName.toLowerCase().indexOf(searchQuery.toLowerCase());
+    let findLength = searchQuery.length;
+    albumNameArray = [
+      albumName.slice(0, findIndex),
+      albumName.slice(findIndex, findIndex + findLength),
+      albumName.slice(findIndex + findLength),
+    ];
+  }
 </script>
 
 <button
-	on:click={() => dispatcher('album')}
-	class="w-full flex gap-4 px-6 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
+  on:click={() => dispatcher('album')}
+  class="w-full flex gap-4 px-6 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
 >
-	<div class="h-12 w-12 rounded-xl bg-slate-300">
-		{#if album.albumThumbnailAssetId}
-			<img
-				src={api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
-				alt={album.albumName}
-				class={`object-cover h-full w-full transition-all z-0 rounded-xl duration-300 hover:shadow-lg`}
-				data-testid="album-image"
-				draggable="false"
-			/>
-		{/if}
-	</div>
-	<div class="h-12 flex flex-col items-start justify-center">
-		<span>{albumNameArray[0]}<b>{albumNameArray[1]}</b>{albumNameArray[2]}</span>
-		<span class="flex gap-1 text-sm">
-			{#if variant === 'simple'}
-				<span
-					>{#if album.shared}Shared{/if}
-				</span>
-			{:else}
-				<span>{album.assetCount} items</span>
-				<span
-					>{#if album.shared} · Shared{/if}
-				</span>
-			{/if}
-		</span>
-	</div>
+  <div class="h-12 w-12 rounded-xl bg-slate-300">
+    {#if album.albumThumbnailAssetId}
+      <img
+        src={api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
+        alt={album.albumName}
+        class={`object-cover h-full w-full transition-all z-0 rounded-xl duration-300 hover:shadow-lg`}
+        data-testid="album-image"
+        draggable="false"
+      />
+    {/if}
+  </div>
+  <div class="h-12 flex flex-col items-start justify-center">
+    <span>{albumNameArray[0]}<b>{albumNameArray[1]}</b>{albumNameArray[2]}</span>
+    <span class="flex gap-1 text-sm">
+      {#if variant === 'simple'}
+        <span
+          >{#if album.shared}Shared{/if}
+        </span>
+      {:else}
+        <span>{album.assetCount} items</span>
+        <span
+          >{#if album.shared} · Shared{/if}
+        </span>
+      {/if}
+    </span>
+  </div>
 </button>
diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
index 241ce2258c..35bab9fa81 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
@@ -1,155 +1,135 @@
 <script lang="ts">
-	import { page } from '$app/stores';
-	import { clickOutside } from '$lib/utils/click-outside';
-	import type { AssetResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
-	import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import Heart from 'svelte-material-icons/Heart.svelte';
-	import HeartOutline from 'svelte-material-icons/HeartOutline.svelte';
-	import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
-	import MagnifyPlusOutline from 'svelte-material-icons/MagnifyPlusOutline.svelte';
-	import MagnifyMinusOutline from 'svelte-material-icons/MagnifyMinusOutline.svelte';
-	import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
-	import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
-	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
-	import { photoZoomState } from '$lib/stores/zoom-image.store';
+  import { page } from '$app/stores';
+  import { clickOutside } from '$lib/utils/click-outside';
+  import type { AssetResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+  import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
+  import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
+  import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import Heart from 'svelte-material-icons/Heart.svelte';
+  import HeartOutline from 'svelte-material-icons/HeartOutline.svelte';
+  import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
+  import MagnifyPlusOutline from 'svelte-material-icons/MagnifyPlusOutline.svelte';
+  import MagnifyMinusOutline from 'svelte-material-icons/MagnifyMinusOutline.svelte';
+  import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
+  import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
+  import MenuOption from '../shared-components/context-menu/menu-option.svelte';
+  import { photoZoomState } from '$lib/stores/zoom-image.store';
 
-	export let asset: AssetResponseDto;
-	export let showCopyButton: boolean;
-	export let showZoomButton: boolean;
-	export let showMotionPlayButton: boolean;
-	export let isMotionPhotoPlaying = false;
-	export let showDownloadButton: boolean;
+  export let asset: AssetResponseDto;
+  export let showCopyButton: boolean;
+  export let showZoomButton: boolean;
+  export let showMotionPlayButton: boolean;
+  export let isMotionPhotoPlaying = false;
+  export let showDownloadButton: boolean;
 
-	const isOwner = asset.ownerId === $page.data.user?.id;
+  const isOwner = asset.ownerId === $page.data.user?.id;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	let contextMenuPosition = { x: 0, y: 0 };
-	let isShowAssetOptions = false;
+  let contextMenuPosition = { x: 0, y: 0 };
+  let isShowAssetOptions = false;
 
-	const showOptionsMenu = ({ x, y }: MouseEvent) => {
-		contextMenuPosition = { x, y };
-		isShowAssetOptions = !isShowAssetOptions;
-	};
+  const showOptionsMenu = ({ x, y }: MouseEvent) => {
+    contextMenuPosition = { x, y };
+    isShowAssetOptions = !isShowAssetOptions;
+  };
 
-	const onMenuClick = (eventName: string) => {
-		isShowAssetOptions = false;
-		dispatch(eventName);
-	};
+  const onMenuClick = (eventName: string) => {
+    isShowAssetOptions = false;
+    dispatch(eventName);
+  };
 </script>
 
 <div
-	class="h-16 flex justify-between place-items-center px-3 transition-transform duration-200 z-[1001] bg-gradient-to-b from-black/40"
+  class="h-16 flex justify-between place-items-center px-3 transition-transform duration-200 z-[1001] bg-gradient-to-b from-black/40"
 >
-	<div class="text-white">
-		<CircleIconButton isOpacity={true} logo={ArrowLeft} on:click={() => dispatch('goBack')} />
-	</div>
-	<div class="text-white flex gap-2 justify-end w-[calc(100%-3rem)] overflow-hidden">
-		{#if showMotionPlayButton}
-			{#if isMotionPhotoPlaying}
-				<CircleIconButton
-					isOpacity={true}
-					logo={MotionPauseOutline}
-					title="Stop Motion Photo"
-					on:click={() => dispatch('stopMotionPhoto')}
-				/>
-			{:else}
-				<CircleIconButton
-					isOpacity={true}
-					logo={MotionPlayOutline}
-					title="Play Motion Photo"
-					on:click={() => dispatch('playMotionPhoto')}
-				/>
-			{/if}
-		{/if}
-		{#if showZoomButton}
-			<CircleIconButton
-				isOpacity={true}
-				hideMobile={true}
-				logo={$photoZoomState && $photoZoomState.currentZoom > 1
-					? MagnifyMinusOutline
-					: MagnifyPlusOutline}
-				title="Zoom Image"
-				on:click={() => {
-					const zoomImage = new CustomEvent('zoomImage');
-					window.dispatchEvent(zoomImage);
-				}}
-			/>
-		{/if}
-		{#if showCopyButton}
-			<CircleIconButton
-				isOpacity={true}
-				logo={ContentCopy}
-				title="Copy Image"
-				on:click={() => {
-					const copyEvent = new CustomEvent('copyImage');
-					window.dispatchEvent(copyEvent);
-				}}
-			/>
-		{/if}
+  <div class="text-white">
+    <CircleIconButton isOpacity={true} logo={ArrowLeft} on:click={() => dispatch('goBack')} />
+  </div>
+  <div class="text-white flex gap-2 justify-end w-[calc(100%-3rem)] overflow-hidden">
+    {#if showMotionPlayButton}
+      {#if isMotionPhotoPlaying}
+        <CircleIconButton
+          isOpacity={true}
+          logo={MotionPauseOutline}
+          title="Stop Motion Photo"
+          on:click={() => dispatch('stopMotionPhoto')}
+        />
+      {:else}
+        <CircleIconButton
+          isOpacity={true}
+          logo={MotionPlayOutline}
+          title="Play Motion Photo"
+          on:click={() => dispatch('playMotionPhoto')}
+        />
+      {/if}
+    {/if}
+    {#if showZoomButton}
+      <CircleIconButton
+        isOpacity={true}
+        hideMobile={true}
+        logo={$photoZoomState && $photoZoomState.currentZoom > 1 ? MagnifyMinusOutline : MagnifyPlusOutline}
+        title="Zoom Image"
+        on:click={() => {
+          const zoomImage = new CustomEvent('zoomImage');
+          window.dispatchEvent(zoomImage);
+        }}
+      />
+    {/if}
+    {#if showCopyButton}
+      <CircleIconButton
+        isOpacity={true}
+        logo={ContentCopy}
+        title="Copy Image"
+        on:click={() => {
+          const copyEvent = new CustomEvent('copyImage');
+          window.dispatchEvent(copyEvent);
+        }}
+      />
+    {/if}
 
-		{#if showDownloadButton}
-			<CircleIconButton
-				isOpacity={true}
-				logo={CloudDownloadOutline}
-				on:click={() => dispatch('download')}
-				title="Download"
-			/>
-		{/if}
-		<CircleIconButton
-			isOpacity={true}
-			logo={InformationOutline}
-			on:click={() => dispatch('showDetail')}
-			title="Info"
-		/>
-		{#if isOwner}
-			<CircleIconButton
-				isOpacity={true}
-				logo={asset.isFavorite ? Heart : HeartOutline}
-				on:click={() => dispatch('favorite')}
-				title="Favorite"
-			/>
-		{/if}
+    {#if showDownloadButton}
+      <CircleIconButton
+        isOpacity={true}
+        logo={CloudDownloadOutline}
+        on:click={() => dispatch('download')}
+        title="Download"
+      />
+    {/if}
+    <CircleIconButton isOpacity={true} logo={InformationOutline} on:click={() => dispatch('showDetail')} title="Info" />
+    {#if isOwner}
+      <CircleIconButton
+        isOpacity={true}
+        logo={asset.isFavorite ? Heart : HeartOutline}
+        on:click={() => dispatch('favorite')}
+        title="Favorite"
+      />
+    {/if}
 
-		{#if isOwner}
-			<CircleIconButton
-				isOpacity={true}
-				logo={DeleteOutline}
-				on:click={() => dispatch('delete')}
-				title="Delete"
-			/>
-			<div use:clickOutside on:outclick={() => (isShowAssetOptions = false)}>
-				<CircleIconButton
-					isOpacity={true}
-					logo={DotsVertical}
-					on:click={showOptionsMenu}
-					title="More"
-				>
-					{#if isShowAssetOptions}
-						<ContextMenu {...contextMenuPosition} direction="left">
-							<MenuOption on:click={() => onMenuClick('addToAlbum')} text="Add to Album" />
-							<MenuOption
-								on:click={() => onMenuClick('addToSharedAlbum')}
-								text="Add to Shared Album"
-							/>
+    {#if isOwner}
+      <CircleIconButton isOpacity={true} logo={DeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
+      <div use:clickOutside on:outclick={() => (isShowAssetOptions = false)}>
+        <CircleIconButton isOpacity={true} logo={DotsVertical} on:click={showOptionsMenu} title="More">
+          {#if isShowAssetOptions}
+            <ContextMenu {...contextMenuPosition} direction="left">
+              <MenuOption on:click={() => onMenuClick('addToAlbum')} text="Add to Album" />
+              <MenuOption on:click={() => onMenuClick('addToSharedAlbum')} text="Add to Shared Album" />
 
-							{#if isOwner}
-								<MenuOption
-									on:click={() => dispatch('toggleArchive')}
-									text={asset.isArchived ? 'Unarchive' : 'Archive'}
-								/>
-							{/if}
-						</ContextMenu>
-					{/if}
-				</CircleIconButton>
-			</div>
-		{/if}
-	</div>
+              {#if isOwner}
+                <MenuOption
+                  on:click={() => dispatch('toggleArchive')}
+                  text={asset.isArchived ? 'Unarchive' : 'Archive'}
+                />
+              {/if}
+            </ContextMenu>
+          {/if}
+        </CircleIconButton>
+      </div>
+    {/if}
+  </div>
 </div>
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index 358572001b..7de3b2f537 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -1,399 +1,386 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import {
-		AlbumResponseDto,
-		api,
-		AssetResponseDto,
-		AssetTypeEnum,
-		SharedLinkResponseDto
-	} from '@api';
-	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
-	import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
-	import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
-	import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
-	import { fly } from 'svelte/transition';
-	import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import AssetViewerNavBar from './asset-viewer-nav-bar.svelte';
-	import DetailPanel from './detail-panel.svelte';
-	import PhotoViewer from './photo-viewer.svelte';
-	import VideoViewer from './video-viewer.svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import { goto } from '$app/navigation';
+  import { AlbumResponseDto, api, AssetResponseDto, AssetTypeEnum, SharedLinkResponseDto } from '@api';
+  import { createEventDispatcher, onDestroy, onMount } from 'svelte';
+  import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
+  import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
+  import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
+  import { fly } from 'svelte/transition';
+  import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import AssetViewerNavBar from './asset-viewer-nav-bar.svelte';
+  import DetailPanel from './detail-panel.svelte';
+  import PhotoViewer from './photo-viewer.svelte';
+  import VideoViewer from './video-viewer.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 
-	import { assetStore } from '$lib/stores/assets.store';
-	import { isShowDetail } from '$lib/stores/preferences.store';
-	import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
-	import { browser } from '$app/environment';
+  import { assetStore } from '$lib/stores/assets.store';
+  import { isShowDetail } from '$lib/stores/preferences.store';
+  import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
+  import { browser } from '$app/environment';
 
-	export let asset: AssetResponseDto;
-	export let publicSharedKey = '';
-	export let showNavigation = true;
-	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
+  export let asset: AssetResponseDto;
+  export let publicSharedKey = '';
+  export let showNavigation = true;
+  export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 
-	const dispatch = createEventDispatcher();
-	let halfLeftHover = false;
-	let halfRightHover = false;
-	let appearsInAlbums: AlbumResponseDto[] = [];
-	let isShowAlbumPicker = false;
-	let isShowDeleteConfirmation = false;
-	let addToSharedAlbum = true;
-	let shouldPlayMotionPhoto = false;
-	let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true;
-	let canCopyImagesToClipboard: boolean;
-	const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key);
+  const dispatch = createEventDispatcher();
+  let halfLeftHover = false;
+  let halfRightHover = false;
+  let appearsInAlbums: AlbumResponseDto[] = [];
+  let isShowAlbumPicker = false;
+  let isShowDeleteConfirmation = false;
+  let addToSharedAlbum = true;
+  let shouldPlayMotionPhoto = false;
+  let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true;
+  let canCopyImagesToClipboard: boolean;
+  const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key);
 
-	onMount(async () => {
-		document.addEventListener('keydown', onKeyboardPress);
+  onMount(async () => {
+    document.addEventListener('keydown', onKeyboardPress);
 
-		getAllAlbums();
+    getAllAlbums();
 
-		// Import hack :( see https://github.com/vadimkorr/svelte-carousel/issues/27#issuecomment-851022295
-		// TODO: Move to regular import once the package correctly supports ESM.
-		const module = await import('copy-image-clipboard');
-		canCopyImagesToClipboard = module.canCopyImagesToClipboard();
-	});
+    // Import hack :( see https://github.com/vadimkorr/svelte-carousel/issues/27#issuecomment-851022295
+    // TODO: Move to regular import once the package correctly supports ESM.
+    const module = await import('copy-image-clipboard');
+    canCopyImagesToClipboard = module.canCopyImagesToClipboard();
+  });
 
-	onDestroy(() => {
-		if (browser) {
-			document.removeEventListener('keydown', onKeyboardPress);
-		}
-	});
+  onDestroy(() => {
+    if (browser) {
+      document.removeEventListener('keydown', onKeyboardPress);
+    }
+  });
 
-	$: asset.id && getAllAlbums(); // Update the album information when the asset ID changes
+  $: asset.id && getAllAlbums(); // Update the album information when the asset ID changes
 
-	const getAllAlbums = async () => {
-		try {
-			const { data } = await api.albumApi.getAllAlbums({ assetId: asset.id });
-			appearsInAlbums = data;
-		} catch (e) {
-			console.error('Error getting album that asset belong to', e);
-		}
-	};
+  const getAllAlbums = async () => {
+    try {
+      const { data } = await api.albumApi.getAllAlbums({ assetId: asset.id });
+      appearsInAlbums = data;
+    } catch (e) {
+      console.error('Error getting album that asset belong to', e);
+    }
+  };
 
-	const handleKeyboardPress = (key: string) => {
-		switch (key) {
-			case 'Escape':
-				closeViewer();
-				return;
-			case 'Delete':
-				isShowDeleteConfirmation = true;
-				return;
-			case 'i':
-				$isShowDetail = !$isShowDetail;
-				return;
-			case 'ArrowLeft':
-				navigateAssetBackward();
-				return;
-			case 'ArrowRight':
-				navigateAssetForward();
-				return;
-		}
-	};
+  const handleKeyboardPress = (key: string) => {
+    switch (key) {
+      case 'Escape':
+        closeViewer();
+        return;
+      case 'Delete':
+        isShowDeleteConfirmation = true;
+        return;
+      case 'i':
+        $isShowDetail = !$isShowDetail;
+        return;
+      case 'ArrowLeft':
+        navigateAssetBackward();
+        return;
+      case 'ArrowRight':
+        navigateAssetForward();
+        return;
+    }
+  };
 
-	const handleCloseViewer = () => {
-		$isShowDetail = false;
-		closeViewer();
-	};
+  const handleCloseViewer = () => {
+    $isShowDetail = false;
+    closeViewer();
+  };
 
-	const closeViewer = () => {
-		dispatch('close');
-	};
+  const closeViewer = () => {
+    dispatch('close');
+  };
 
-	const navigateAssetForward = (e?: Event) => {
-		e?.stopPropagation();
-		dispatch('navigate-next');
-	};
+  const navigateAssetForward = (e?: Event) => {
+    e?.stopPropagation();
+    dispatch('navigate-next');
+  };
 
-	const navigateAssetBackward = (e?: Event) => {
-		e?.stopPropagation();
-		dispatch('navigate-previous');
-	};
+  const navigateAssetBackward = (e?: Event) => {
+    e?.stopPropagation();
+    dispatch('navigate-previous');
+  };
 
-	const showDetailInfoHandler = () => {
-		$isShowDetail = !$isShowDetail;
-	};
+  const showDetailInfoHandler = () => {
+    $isShowDetail = !$isShowDetail;
+  };
 
-	const deleteAsset = async () => {
-		try {
-			const { data: deletedAssets } = await api.assetApi.deleteAsset({
-				deleteAssetDto: {
-					ids: [asset.id]
-				}
-			});
+  const deleteAsset = async () => {
+    try {
+      const { data: deletedAssets } = await api.assetApi.deleteAsset({
+        deleteAssetDto: {
+          ids: [asset.id],
+        },
+      });
 
-			navigateAssetForward();
+      navigateAssetForward();
 
-			for (const asset of deletedAssets) {
-				if (asset.status == 'SUCCESS') {
-					assetStore.removeAsset(asset.id);
-				}
-			}
-		} catch (e) {
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error deleting this asset, check console for more details'
-			});
-			console.error('Error deleteAsset', e);
-		} finally {
-			isShowDeleteConfirmation = false;
-		}
-	};
+      for (const asset of deletedAssets) {
+        if (asset.status == 'SUCCESS') {
+          assetStore.removeAsset(asset.id);
+        }
+      }
+    } catch (e) {
+      notificationController.show({
+        type: NotificationType.Error,
+        message: 'Error deleting this asset, check console for more details',
+      });
+      console.error('Error deleteAsset', e);
+    } finally {
+      isShowDeleteConfirmation = false;
+    }
+  };
 
-	const toggleFavorite = async () => {
-		const { data } = await api.assetApi.updateAsset({
-			id: asset.id,
-			updateAssetDto: {
-				isFavorite: !asset.isFavorite
-			}
-		});
+  const toggleFavorite = async () => {
+    const { data } = await api.assetApi.updateAsset({
+      id: asset.id,
+      updateAssetDto: {
+        isFavorite: !asset.isFavorite,
+      },
+    });
 
-		asset.isFavorite = data.isFavorite;
-		assetStore.updateAsset(asset.id, data.isFavorite);
-	};
+    asset.isFavorite = data.isFavorite;
+    assetStore.updateAsset(asset.id, data.isFavorite);
+  };
 
-	const openAlbumPicker = (shared: boolean) => {
-		isShowAlbumPicker = true;
-		addToSharedAlbum = shared;
-	};
+  const openAlbumPicker = (shared: boolean) => {
+    isShowAlbumPicker = true;
+    addToSharedAlbum = shared;
+  };
 
-	const handleAddToNewAlbum = (event: CustomEvent) => {
-		isShowAlbumPicker = false;
+  const handleAddToNewAlbum = (event: CustomEvent) => {
+    isShowAlbumPicker = false;
 
-		const { albumName }: { albumName: string } = event.detail;
-		api.albumApi
-			.createAlbum({ createAlbumDto: { albumName, assetIds: [asset.id] } })
-			.then((response) => {
-				const album = response.data;
-				goto('/albums/' + album.id);
-			});
-	};
+    const { albumName }: { albumName: string } = event.detail;
+    api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds: [asset.id] } }).then((response) => {
+      const album = response.data;
+      goto('/albums/' + album.id);
+    });
+  };
 
-	const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
-		isShowAlbumPicker = false;
-		const album = event.detail.album;
+  const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
+    isShowAlbumPicker = false;
+    const album = event.detail.album;
 
-		addAssetsToAlbum(album.id, [asset.id]).then((dto) => {
-			if (dto.successfullyAdded === 1 && dto.album) {
-				appearsInAlbums = [...appearsInAlbums, dto.album];
-			}
-		});
-	};
+    addAssetsToAlbum(album.id, [asset.id]).then((dto) => {
+      if (dto.successfullyAdded === 1 && dto.album) {
+        appearsInAlbums = [...appearsInAlbums, dto.album];
+      }
+    });
+  };
 
-	const disableKeyDownEvent = () => {
-		if (browser) {
-			document.removeEventListener('keydown', onKeyboardPress);
-		}
-	};
+  const disableKeyDownEvent = () => {
+    if (browser) {
+      document.removeEventListener('keydown', onKeyboardPress);
+    }
+  };
 
-	const enableKeyDownEvent = () => {
-		if (browser) {
-			document.addEventListener('keydown', onKeyboardPress);
-		}
-	};
+  const enableKeyDownEvent = () => {
+    if (browser) {
+      document.addEventListener('keydown', onKeyboardPress);
+    }
+  };
 
-	const toggleArchive = async () => {
-		try {
-			const { data } = await api.assetApi.updateAsset({
-				id: asset.id,
-				updateAssetDto: {
-					isArchived: !asset.isArchived
-				}
-			});
+  const toggleArchive = async () => {
+    try {
+      const { data } = await api.assetApi.updateAsset({
+        id: asset.id,
+        updateAssetDto: {
+          isArchived: !asset.isArchived,
+        },
+      });
 
-			asset.isArchived = data.isArchived;
+      asset.isArchived = data.isArchived;
 
-			if (data.isArchived) {
-				dispatch('archived', data);
-			} else {
-				dispatch('unarchived', data);
-			}
+      if (data.isArchived) {
+        dispatch('archived', data);
+      } else {
+        dispatch('unarchived', data);
+      }
 
-			notificationController.show({
-				type: NotificationType.Info,
-				message: asset.isArchived ? `Added to archive` : `Removed from archive`
-			});
-		} catch (error) {
-			console.error(error);
-			notificationController.show({
-				type: NotificationType.Error,
-				message: `Error ${
-					asset.isArchived ? 'archiving' : 'unarchiving'
-				} asset, check console for more details`
-			});
-		}
-	};
+      notificationController.show({
+        type: NotificationType.Info,
+        message: asset.isArchived ? `Added to archive` : `Removed from archive`,
+      });
+    } catch (error) {
+      console.error(error);
+      notificationController.show({
+        type: NotificationType.Error,
+        message: `Error ${asset.isArchived ? 'archiving' : 'unarchiving'} asset, check console for more details`,
+      });
+    }
+  };
 
-	const getAssetType = () => {
-		switch (asset.type) {
-			case 'IMAGE':
-				return 'Photo';
-			case 'VIDEO':
-				return 'Video';
-			default:
-				return 'Asset';
-		}
-	};
+  const getAssetType = () => {
+    switch (asset.type) {
+      case 'IMAGE':
+        return 'Photo';
+      case 'VIDEO':
+        return 'Video';
+      default:
+        return 'Asset';
+    }
+  };
 </script>
 
 <section
-	id="immich-asset-viewer"
-	class="fixed h-screen w-screen left-0 top-0 overflow-y-hidden bg-black z-[1001] grid grid-rows-[64px_1fr] grid-cols-4"
+  id="immich-asset-viewer"
+  class="fixed h-screen w-screen left-0 top-0 overflow-y-hidden bg-black z-[1001] grid grid-rows-[64px_1fr] grid-cols-4"
 >
-	<div class="col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform">
-		<AssetViewerNavBar
-			{asset}
-			isMotionPhotoPlaying={shouldPlayMotionPhoto}
-			showCopyButton={canCopyImagesToClipboard && asset.type === AssetTypeEnum.Image}
-			showZoomButton={asset.type === AssetTypeEnum.Image}
-			showMotionPlayButton={!!asset.livePhotoVideoId}
-			showDownloadButton={shouldShowDownloadButton}
-			on:goBack={closeViewer}
-			on:showDetail={showDetailInfoHandler}
-			on:download={() => downloadFile(asset, publicSharedKey)}
-			on:delete={() => (isShowDeleteConfirmation = true)}
-			on:favorite={toggleFavorite}
-			on:addToAlbum={() => openAlbumPicker(false)}
-			on:addToSharedAlbum={() => openAlbumPicker(true)}
-			on:playMotionPhoto={() => (shouldPlayMotionPhoto = true)}
-			on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
-			on:toggleArchive={toggleArchive}
-		/>
-	</div>
+  <div class="col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform">
+    <AssetViewerNavBar
+      {asset}
+      isMotionPhotoPlaying={shouldPlayMotionPhoto}
+      showCopyButton={canCopyImagesToClipboard && asset.type === AssetTypeEnum.Image}
+      showZoomButton={asset.type === AssetTypeEnum.Image}
+      showMotionPlayButton={!!asset.livePhotoVideoId}
+      showDownloadButton={shouldShowDownloadButton}
+      on:goBack={closeViewer}
+      on:showDetail={showDetailInfoHandler}
+      on:download={() => downloadFile(asset, publicSharedKey)}
+      on:delete={() => (isShowDeleteConfirmation = true)}
+      on:favorite={toggleFavorite}
+      on:addToAlbum={() => openAlbumPicker(false)}
+      on:addToSharedAlbum={() => openAlbumPicker(true)}
+      on:playMotionPhoto={() => (shouldPlayMotionPhoto = true)}
+      on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
+      on:toggleArchive={toggleArchive}
+    />
+  </div>
 
-	{#if showNavigation}
-		<div
-			class={`row-start-2 row-span-end col-start-1 flex place-items-center hover:cursor-pointer w-1/4 mb-[60px] ${
-				asset.type === AssetTypeEnum.Video ? '' : 'z-[999]'
-			}`}
-			on:mouseenter={() => {
-				halfLeftHover = true;
-				halfRightHover = false;
-			}}
-			on:mouseleave={() => {
-				halfLeftHover = false;
-			}}
-			on:click={navigateAssetBackward}
-			on:keydown={navigateAssetBackward}
-		>
-			<button
-				class="rounded-full p-3 hover:bg-gray-500 hover:text-gray-700 z-[1000] text-gray-500 mx-4"
-				class:navigation-button-hover={halfLeftHover}
-				on:click={navigateAssetBackward}
-			>
-				<ChevronLeft size="36" />
-			</button>
-		</div>
-	{/if}
+  {#if showNavigation}
+    <div
+      class={`row-start-2 row-span-end col-start-1 flex place-items-center hover:cursor-pointer w-1/4 mb-[60px] ${
+        asset.type === AssetTypeEnum.Video ? '' : 'z-[999]'
+      }`}
+      on:mouseenter={() => {
+        halfLeftHover = true;
+        halfRightHover = false;
+      }}
+      on:mouseleave={() => {
+        halfLeftHover = false;
+      }}
+      on:click={navigateAssetBackward}
+      on:keydown={navigateAssetBackward}
+    >
+      <button
+        class="rounded-full p-3 hover:bg-gray-500 hover:text-gray-700 z-[1000] text-gray-500 mx-4"
+        class:navigation-button-hover={halfLeftHover}
+        on:click={navigateAssetBackward}
+      >
+        <ChevronLeft size="36" />
+      </button>
+    </div>
+  {/if}
 
-	<div class="row-start-1 row-span-full col-start-1 col-span-4">
-		{#key asset.id}
-			{#if !asset.resized}
-				<div class="h-full w-full flex justify-center">
-					<div
-						class="h-full bg-gray-100 dark:bg-immich-dark-gray flex items-center justify-center aspect-square px-auto"
-					>
-						<ImageBrokenVariant size="25%" />
-					</div>
-				</div>
-			{:else if asset.type === AssetTypeEnum.Image}
-				{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
-					<VideoViewer
-						{publicSharedKey}
-						assetId={asset.livePhotoVideoId}
-						on:close={closeViewer}
-						on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
-					/>
-				{:else}
-					<PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
-				{/if}
-			{:else}
-				<VideoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
-			{/if}
-		{/key}
-	</div>
+  <div class="row-start-1 row-span-full col-start-1 col-span-4">
+    {#key asset.id}
+      {#if !asset.resized}
+        <div class="h-full w-full flex justify-center">
+          <div
+            class="h-full bg-gray-100 dark:bg-immich-dark-gray flex items-center justify-center aspect-square px-auto"
+          >
+            <ImageBrokenVariant size="25%" />
+          </div>
+        </div>
+      {:else if asset.type === AssetTypeEnum.Image}
+        {#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
+          <VideoViewer
+            {publicSharedKey}
+            assetId={asset.livePhotoVideoId}
+            on:close={closeViewer}
+            on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
+          />
+        {:else}
+          <PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
+        {/if}
+      {:else}
+        <VideoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
+      {/if}
+    {/key}
+  </div>
 
-	{#if showNavigation}
-		<div
-			class={`row-start-2 row-span-full col-start-4 flex justify-end place-items-center hover:cursor-pointer w-1/4 justify-self-end mb-[60px] ${
-				asset.type === AssetTypeEnum.Video ? '' : 'z-[500]'
-			}`}
-			on:click={navigateAssetForward}
-			on:keydown={navigateAssetForward}
-			on:mouseenter={() => {
-				halfLeftHover = false;
-				halfRightHover = true;
-			}}
-			on:mouseleave={() => {
-				halfRightHover = false;
-			}}
-		>
-			<button
-				class="rounded-full p-3 hover:bg-gray-500 hover:text-white text-gray-500 mx-4"
-				class:navigation-button-hover={halfRightHover}
-				on:click={navigateAssetForward}
-			>
-				<ChevronRight size="36" />
-			</button>
-		</div>
-	{/if}
+  {#if showNavigation}
+    <div
+      class={`row-start-2 row-span-full col-start-4 flex justify-end place-items-center hover:cursor-pointer w-1/4 justify-self-end mb-[60px] ${
+        asset.type === AssetTypeEnum.Video ? '' : 'z-[500]'
+      }`}
+      on:click={navigateAssetForward}
+      on:keydown={navigateAssetForward}
+      on:mouseenter={() => {
+        halfLeftHover = false;
+        halfRightHover = true;
+      }}
+      on:mouseleave={() => {
+        halfRightHover = false;
+      }}
+    >
+      <button
+        class="rounded-full p-3 hover:bg-gray-500 hover:text-white text-gray-500 mx-4"
+        class:navigation-button-hover={halfRightHover}
+        on:click={navigateAssetForward}
+      >
+        <ChevronRight size="36" />
+      </button>
+    </div>
+  {/if}
 
-	{#if $isShowDetail}
-		<div
-			transition:fly={{ duration: 150 }}
-			id="detail-panel"
-			class="bg-immich-bg w-[360px] z-[1002] row-span-full transition-all overflow-y-auto dark:bg-immich-dark-bg dark:border-l dark:border-l-immich-dark-gray"
-			translate="yes"
-		>
-			<DetailPanel
-				{asset}
-				albums={appearsInAlbums}
-				on:close={() => ($isShowDetail = false)}
-				on:close-viewer={handleCloseViewer}
-				on:description-focus-in={disableKeyDownEvent}
-				on:description-focus-out={enableKeyDownEvent}
-			/>
-		</div>
-	{/if}
+  {#if $isShowDetail}
+    <div
+      transition:fly={{ duration: 150 }}
+      id="detail-panel"
+      class="bg-immich-bg w-[360px] z-[1002] row-span-full transition-all overflow-y-auto dark:bg-immich-dark-bg dark:border-l dark:border-l-immich-dark-gray"
+      translate="yes"
+    >
+      <DetailPanel
+        {asset}
+        albums={appearsInAlbums}
+        on:close={() => ($isShowDetail = false)}
+        on:close-viewer={handleCloseViewer}
+        on:description-focus-in={disableKeyDownEvent}
+        on:description-focus-out={enableKeyDownEvent}
+      />
+    </div>
+  {/if}
 
-	{#if isShowAlbumPicker}
-		<AlbumSelectionModal
-			shared={addToSharedAlbum}
-			on:newAlbum={handleAddToNewAlbum}
-			on:newSharedAlbum={handleAddToNewAlbum}
-			on:album={handleAddToAlbum}
-			on:close={() => (isShowAlbumPicker = false)}
-		/>
-	{/if}
+  {#if isShowAlbumPicker}
+    <AlbumSelectionModal
+      shared={addToSharedAlbum}
+      on:newAlbum={handleAddToNewAlbum}
+      on:newSharedAlbum={handleAddToNewAlbum}
+      on:album={handleAddToAlbum}
+      on:close={() => (isShowAlbumPicker = false)}
+    />
+  {/if}
 
-	{#if isShowDeleteConfirmation}
-		<ConfirmDialogue
-			title="Delete {getAssetType()}"
-			confirmText="Delete"
-			on:confirm={deleteAsset}
-			on:cancel={() => (isShowDeleteConfirmation = false)}
-		>
-			<svelte:fragment slot="prompt">
-				<p>
-					Are you sure you want to delete this {getAssetType().toLowerCase()}? This will also remove
-					it from its album(s).
-				</p>
-				<p><b>You cannot undo this action!</b></p>
-			</svelte:fragment>
-		</ConfirmDialogue>
-	{/if}
+  {#if isShowDeleteConfirmation}
+    <ConfirmDialogue
+      title="Delete {getAssetType()}"
+      confirmText="Delete"
+      on:confirm={deleteAsset}
+      on:cancel={() => (isShowDeleteConfirmation = false)}
+    >
+      <svelte:fragment slot="prompt">
+        <p>
+          Are you sure you want to delete this {getAssetType().toLowerCase()}? This will also remove it from its
+          album(s).
+        </p>
+        <p><b>You cannot undo this action!</b></p>
+      </svelte:fragment>
+    </ConfirmDialogue>
+  {/if}
 </section>
 
 <style>
-	#immich-asset-viewer {
-		contain: layout;
-	}
+  #immich-asset-viewer {
+    contain: layout;
+  }
 
-	.navigation-button-hover {
-		background-color: rgb(107 114 128 / var(--tw-bg-opacity));
-		color: rgb(255 255 255 / var(--tw-text-opacity));
-		transition: all 150ms;
-	}
+  .navigation-button-hover {
+    background-color: rgb(107 114 128 / var(--tw-bg-opacity));
+    color: rgb(255 255 255 / var(--tw-text-opacity));
+    transition: all 150ms;
+  }
 </style>
diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte
index 365c9bfa38..42bfb6de75 100644
--- a/web/src/lib/components/asset-viewer/detail-panel.svelte
+++ b/web/src/lib/components/asset-viewer/detail-panel.svelte
@@ -1,296 +1,293 @@
 <script lang="ts">
-	import { page } from '$app/stores';
-	import { locale } from '$lib/stores/preferences.store';
-	import type { LatLngTuple } from 'leaflet';
-	import { DateTime } from 'luxon';
-	import Calendar from 'svelte-material-icons/Calendar.svelte';
-	import CameraIris from 'svelte-material-icons/CameraIris.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
-	import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
-	import { createEventDispatcher } from 'svelte';
-	import { AssetResponseDto, AlbumResponseDto, api, ThumbnailFormat } from '@api';
-	import { asByteUnitString } from '../../utils/byte-units';
-	import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
-	import { getAssetFilename } from '$lib/utils/asset-utils';
+  import { page } from '$app/stores';
+  import { locale } from '$lib/stores/preferences.store';
+  import type { LatLngTuple } from 'leaflet';
+  import { DateTime } from 'luxon';
+  import Calendar from 'svelte-material-icons/Calendar.svelte';
+  import CameraIris from 'svelte-material-icons/CameraIris.svelte';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
+  import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
+  import { createEventDispatcher } from 'svelte';
+  import { AssetResponseDto, AlbumResponseDto, api, ThumbnailFormat } from '@api';
+  import { asByteUnitString } from '../../utils/byte-units';
+  import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
+  import { getAssetFilename } from '$lib/utils/asset-utils';
 
-	export let asset: AssetResponseDto;
-	export let albums: AlbumResponseDto[] = [];
-	let textarea: HTMLTextAreaElement;
-	let description: string;
+  export let asset: AssetResponseDto;
+  export let albums: AlbumResponseDto[] = [];
+  let textarea: HTMLTextAreaElement;
+  let description: string;
 
-	$: {
-		// Get latest description from server
-		if (asset.id) {
-			api.assetApi.getAssetById({ id: asset.id }).then((res) => {
-				people = res.data?.people || [];
-				textarea.value = res.data?.exifInfo?.description || '';
-			});
-		}
-	}
+  $: {
+    // Get latest description from server
+    if (asset.id) {
+      api.assetApi.getAssetById({ id: asset.id }).then((res) => {
+        people = res.data?.people || [];
+        textarea.value = res.data?.exifInfo?.description || '';
+      });
+    }
+  }
 
-	$: latlng = (() => {
-		const lat = asset.exifInfo?.latitude;
-		const lng = asset.exifInfo?.longitude;
+  $: latlng = (() => {
+    const lat = asset.exifInfo?.latitude;
+    const lng = asset.exifInfo?.longitude;
 
-		if (lat && lng) {
-			return [lat, lng] as LatLngTuple;
-		}
-	})();
+    if (lat && lng) {
+      return [lat, lng] as LatLngTuple;
+    }
+  })();
 
-	$: people = asset.people || [];
+  $: people = asset.people || [];
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	const getMegapixel = (width: number, height: number): number | undefined => {
-		const megapixel = Math.round((height * width) / 1_000_000);
+  const getMegapixel = (width: number, height: number): number | undefined => {
+    const megapixel = Math.round((height * width) / 1_000_000);
 
-		if (megapixel) {
-			return megapixel;
-		}
+    if (megapixel) {
+      return megapixel;
+    }
 
-		return undefined;
-	};
+    return undefined;
+  };
 
-	const autoGrowHeight = (e: Event) => {
-		const target = e.target as HTMLTextAreaElement;
-		target.style.height = 'auto';
-		target.style.height = `${target.scrollHeight}px`;
-	};
+  const autoGrowHeight = (e: Event) => {
+    const target = e.target as HTMLTextAreaElement;
+    target.style.height = 'auto';
+    target.style.height = `${target.scrollHeight}px`;
+  };
 
-	const handleFocusIn = () => {
-		dispatch('description-focus-in');
-	};
+  const handleFocusIn = () => {
+    dispatch('description-focus-in');
+  };
 
-	const handleFocusOut = async () => {
-		dispatch('description-focus-out');
-		try {
-			await api.assetApi.updateAsset({
-				id: asset.id,
-				updateAssetDto: {
-					description: description
-				}
-			});
-		} catch (error) {
-			console.error(error);
-		}
-	};
+  const handleFocusOut = async () => {
+    dispatch('description-focus-out');
+    try {
+      await api.assetApi.updateAsset({
+        id: asset.id,
+        updateAssetDto: {
+          description: description,
+        },
+      });
+    } catch (error) {
+      console.error(error);
+    }
+  };
 </script>
 
 <section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
-	<div class="flex place-items-center gap-2">
-		<button
-			class="rounded-full p-3 flex place-items-center place-content-center hover:bg-gray-200 transition-colors dark:text-immich-dark-fg dark:hover:bg-gray-900"
-			on:click={() => dispatch('close')}
-		>
-			<Close size="24" />
-		</button>
+  <div class="flex place-items-center gap-2">
+    <button
+      class="rounded-full p-3 flex place-items-center place-content-center hover:bg-gray-200 transition-colors dark:text-immich-dark-fg dark:hover:bg-gray-900"
+      on:click={() => dispatch('close')}
+    >
+      <Close size="24" />
+    </button>
 
-		<p class="text-immich-fg dark:text-immich-dark-fg text-lg">Info</p>
-	</div>
+    <p class="text-immich-fg dark:text-immich-dark-fg text-lg">Info</p>
+  </div>
 
-	<section class="mx-4 mt-10">
-		<textarea
-			bind:this={textarea}
-			class="max-h-[500px]
+  <section class="mx-4 mt-10">
+    <textarea
+      bind:this={textarea}
+      class="max-h-[500px]
       text-base text-black bg-transparent dark:text-white border-b focus:border-b-2 border-gray-500 w-full focus:border-immich-primary dark:focus:border-immich-dark-primary transition-all resize-none overflow-hidden outline-none disabled:border-none"
-			placeholder={$page?.data?.user?.id !== asset.ownerId ? '' : 'Add a description'}
-			style:display={$page?.data?.user?.id !== asset.ownerId && textarea?.value == ''
-				? 'none'
-				: 'block'}
-			on:focusin={handleFocusIn}
-			on:focusout={handleFocusOut}
-			on:input={autoGrowHeight}
-			bind:value={description}
-			disabled={$page?.data?.user?.id !== asset.ownerId}
-		/>
-	</section>
+      placeholder={$page?.data?.user?.id !== asset.ownerId ? '' : 'Add a description'}
+      style:display={$page?.data?.user?.id !== asset.ownerId && textarea?.value == '' ? 'none' : 'block'}
+      on:focusin={handleFocusIn}
+      on:focusout={handleFocusOut}
+      on:input={autoGrowHeight}
+      bind:value={description}
+      disabled={$page?.data?.user?.id !== asset.ownerId}
+    />
+  </section>
 
-	{#if people.length > 0}
-		<section class="px-4 py-4 text-sm">
-			<h2>PEOPLE</h2>
+  {#if people.length > 0}
+    <section class="px-4 py-4 text-sm">
+      <h2>PEOPLE</h2>
 
-			<div class="flex flex-wrap gap-2 mt-4">
-				{#each people as person (person.id)}
-					<a href="/people/{person.id}" class="w-[90px]" on:click={() => dispatch('close-viewer')}>
-						<ImageThumbnail
-							curve
-							shadow
-							url={api.getPeopleThumbnailUrl(person.id)}
-							altText={person.name}
-							widthStyle="90px"
-							heightStyle="90px"
-							thumbhash={null}
-						/>
-						<p class="font-medium mt-1 truncate">{person.name}</p>
-					</a>
-				{/each}
-			</div>
-		</section>
-	{/if}
+      <div class="flex flex-wrap gap-2 mt-4">
+        {#each people as person (person.id)}
+          <a href="/people/{person.id}" class="w-[90px]" on:click={() => dispatch('close-viewer')}>
+            <ImageThumbnail
+              curve
+              shadow
+              url={api.getPeopleThumbnailUrl(person.id)}
+              altText={person.name}
+              widthStyle="90px"
+              heightStyle="90px"
+              thumbhash={null}
+            />
+            <p class="font-medium mt-1 truncate">{person.name}</p>
+          </a>
+        {/each}
+      </div>
+    </section>
+  {/if}
 
-	<div class="px-4 py-4">
-		{#if !asset.exifInfo}
-			<p class="text-sm">NO EXIF INFO AVAILABLE</p>
-		{:else}
-			<p class="text-sm">DETAILS</p>
-		{/if}
+  <div class="px-4 py-4">
+    {#if !asset.exifInfo}
+      <p class="text-sm">NO EXIF INFO AVAILABLE</p>
+    {:else}
+      <p class="text-sm">DETAILS</p>
+    {/if}
 
-		{#if asset.exifInfo?.dateTimeOriginal}
-			{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
-				zone: asset.exifInfo.timeZone ?? undefined
-			})}
-			<div class="flex gap-4 py-4">
-				<div>
-					<Calendar size="24" />
-				</div>
+    {#if asset.exifInfo?.dateTimeOriginal}
+      {@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
+        zone: asset.exifInfo.timeZone ?? undefined,
+      })}
+      <div class="flex gap-4 py-4">
+        <div>
+          <Calendar size="24" />
+        </div>
 
-				<div>
-					<p>
-						{assetDateTimeOriginal.toLocaleString(
-							{
-								month: 'short',
-								day: 'numeric',
-								year: 'numeric'
-							},
-							{ locale: $locale }
-						)}
-					</p>
-					<div class="flex gap-2 text-sm">
-						<p>
-							{assetDateTimeOriginal.toLocaleString(
-								{
-									weekday: 'short',
-									hour: 'numeric',
-									minute: '2-digit',
-									timeZoneName: 'longOffset'
-								},
-								{ locale: $locale }
-							)}
-						</p>
-					</div>
-				</div>
-			</div>{/if}
+        <div>
+          <p>
+            {assetDateTimeOriginal.toLocaleString(
+              {
+                month: 'short',
+                day: 'numeric',
+                year: 'numeric',
+              },
+              { locale: $locale },
+            )}
+          </p>
+          <div class="flex gap-2 text-sm">
+            <p>
+              {assetDateTimeOriginal.toLocaleString(
+                {
+                  weekday: 'short',
+                  hour: 'numeric',
+                  minute: '2-digit',
+                  timeZoneName: 'longOffset',
+                },
+                { locale: $locale },
+              )}
+            </p>
+          </div>
+        </div>
+      </div>{/if}
 
-		{#if asset.exifInfo?.fileSizeInByte}
-			<div class="flex gap-4 py-4">
-				<div><ImageOutline size="24" /></div>
+    {#if asset.exifInfo?.fileSizeInByte}
+      <div class="flex gap-4 py-4">
+        <div><ImageOutline size="24" /></div>
 
-				<div>
-					<p class="break-all">
-						{getAssetFilename(asset)}
-					</p>
-					<div class="flex text-sm gap-2">
-						{#if asset.exifInfo.exifImageHeight && asset.exifInfo.exifImageWidth}
-							{#if getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)}
-								<p>
-									{getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)} MP
-								</p>
-							{/if}
+        <div>
+          <p class="break-all">
+            {getAssetFilename(asset)}
+          </p>
+          <div class="flex text-sm gap-2">
+            {#if asset.exifInfo.exifImageHeight && asset.exifInfo.exifImageWidth}
+              {#if getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)}
+                <p>
+                  {getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)} MP
+                </p>
+              {/if}
 
-							<p>{asset.exifInfo.exifImageHeight} x {asset.exifInfo.exifImageWidth}</p>
-						{/if}
-						<p>{asByteUnitString(asset.exifInfo.fileSizeInByte, $locale)}</p>
-					</div>
-				</div>
-			</div>
-		{/if}
+              <p>{asset.exifInfo.exifImageHeight} x {asset.exifInfo.exifImageWidth}</p>
+            {/if}
+            <p>{asByteUnitString(asset.exifInfo.fileSizeInByte, $locale)}</p>
+          </div>
+        </div>
+      </div>
+    {/if}
 
-		{#if asset.exifInfo?.fNumber}
-			<div class="flex gap-4 py-4">
-				<div><CameraIris size="24" /></div>
+    {#if asset.exifInfo?.fNumber}
+      <div class="flex gap-4 py-4">
+        <div><CameraIris size="24" /></div>
 
-				<div>
-					<p>{asset.exifInfo.make || ''} {asset.exifInfo.model || ''}</p>
-					<div class="flex text-sm gap-2">
-						<p>{`Æ’/${asset.exifInfo.fNumber.toLocaleString($locale)}` || ''}</p>
+        <div>
+          <p>{asset.exifInfo.make || ''} {asset.exifInfo.model || ''}</p>
+          <div class="flex text-sm gap-2">
+            <p>{`Æ’/${asset.exifInfo.fNumber.toLocaleString($locale)}` || ''}</p>
 
-						{#if asset.exifInfo.exposureTime}
-							<p>{`${asset.exifInfo.exposureTime}`}</p>
-						{/if}
+            {#if asset.exifInfo.exposureTime}
+              <p>{`${asset.exifInfo.exposureTime}`}</p>
+            {/if}
 
-						{#if asset.exifInfo.focalLength}
-							<p>{`${asset.exifInfo.focalLength.toLocaleString($locale)} mm`}</p>
-						{/if}
+            {#if asset.exifInfo.focalLength}
+              <p>{`${asset.exifInfo.focalLength.toLocaleString($locale)} mm`}</p>
+            {/if}
 
-						{#if asset.exifInfo.iso}
-							<p>
-								{`ISO${asset.exifInfo.iso}`}
-							</p>
-						{/if}
-					</div>
-				</div>
-			</div>
-		{/if}
+            {#if asset.exifInfo.iso}
+              <p>
+                {`ISO${asset.exifInfo.iso}`}
+              </p>
+            {/if}
+          </div>
+        </div>
+      </div>
+    {/if}
 
-		{#if asset.exifInfo?.city}
-			<div class="flex gap-4 py-4">
-				<div><MapMarkerOutline size="24" /></div>
+    {#if asset.exifInfo?.city}
+      <div class="flex gap-4 py-4">
+        <div><MapMarkerOutline size="24" /></div>
 
-				<div>
-					<p>{asset.exifInfo.city}</p>
-					<div class="flex text-sm gap-2">
-						<p>{asset.exifInfo.state}</p>
-					</div>
-					<div class="flex text-sm gap-2">
-						<p>{asset.exifInfo.country}</p>
-					</div>
-				</div>
-			</div>
-		{/if}
-	</div>
+        <div>
+          <p>{asset.exifInfo.city}</p>
+          <div class="flex text-sm gap-2">
+            <p>{asset.exifInfo.state}</p>
+          </div>
+          <div class="flex text-sm gap-2">
+            <p>{asset.exifInfo.country}</p>
+          </div>
+        </div>
+      </div>
+    {/if}
+  </div>
 </section>
 
 {#if latlng}
-	<div class="h-[360px]">
-		{#await import('../shared-components/leaflet') then { Map, TileLayer, Marker }}
-			<Map center={latlng} zoom={14}>
-				<TileLayer
-					urlTemplate={'https://tile.openstreetmap.org/{z}/{x}/{y}.png'}
-					options={{
-						attribution:
-							'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
-					}}
-				/>
-				<Marker {latlng} popupContent="{latlng[0].toFixed(7)},{latlng[1].toFixed(7)}" />
-			</Map>
-		{/await}
-	</div>
+  <div class="h-[360px]">
+    {#await import('../shared-components/leaflet') then { Map, TileLayer, Marker }}
+      <Map center={latlng} zoom={14}>
+        <TileLayer
+          urlTemplate={'https://tile.openstreetmap.org/{z}/{x}/{y}.png'}
+          options={{
+            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
+          }}
+        />
+        <Marker {latlng} popupContent="{latlng[0].toFixed(7)},{latlng[1].toFixed(7)}" />
+      </Map>
+    {/await}
+  </div>
 {/if}
 
 <section class="p-2 dark:text-immich-dark-fg">
-	<div class="px-4 py-4">
-		{#if albums.length > 0}
-			<p class="text-sm pb-4">APPEARS IN</p>
-		{/if}
-		{#each albums as album}
-			<a data-sveltekit-preload-data="hover" href={`/albums/${album.id}`}>
-				<div
-					class="flex gap-4 py-2 hover:cursor-pointer"
-					on:click={() => dispatch('click', album)}
-					on:keydown={() => dispatch('click', album)}
-				>
-					<div>
-						<img
-							alt={album.albumName}
-							class="w-[50px] h-[50px] object-cover rounded"
-							src={album.albumThumbnailAssetId &&
-								api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
-							draggable="false"
-						/>
-					</div>
+  <div class="px-4 py-4">
+    {#if albums.length > 0}
+      <p class="text-sm pb-4">APPEARS IN</p>
+    {/if}
+    {#each albums as album}
+      <a data-sveltekit-preload-data="hover" href={`/albums/${album.id}`}>
+        <div
+          class="flex gap-4 py-2 hover:cursor-pointer"
+          on:click={() => dispatch('click', album)}
+          on:keydown={() => dispatch('click', album)}
+        >
+          <div>
+            <img
+              alt={album.albumName}
+              class="w-[50px] h-[50px] object-cover rounded"
+              src={album.albumThumbnailAssetId &&
+                api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
+              draggable="false"
+            />
+          </div>
 
-					<div class="mt-auto mb-auto">
-						<p class="dark:text-immich-dark-primary">{album.albumName}</p>
-						<div class="flex gap-2 text-sm">
-							<p>{album.assetCount} items</p>
-							{#if album.shared}
-								<p>· Shared</p>
-							{/if}
-						</div>
-					</div>
-				</div>
-			</a>
-		{/each}
-	</div>
+          <div class="mt-auto mb-auto">
+            <p class="dark:text-immich-dark-primary">{album.albumName}</p>
+            <div class="flex gap-2 text-sm">
+              <p>{album.assetCount} items</p>
+              {#if album.shared}
+                <p>· Shared</p>
+              {/if}
+            </div>
+          </div>
+        </div>
+      </a>
+    {/each}
+  </div>
 </section>
diff --git a/web/src/lib/components/asset-viewer/download-panel.svelte b/web/src/lib/components/asset-viewer/download-panel.svelte
index a8c1b46dce..3f0c872d2e 100644
--- a/web/src/lib/components/asset-viewer/download-panel.svelte
+++ b/web/src/lib/components/asset-viewer/download-panel.svelte
@@ -1,31 +1,28 @@
 <script lang="ts">
-	import { downloadAssets, isDownloading } from '$lib/stores/download';
-	import { fly, slide } from 'svelte/transition';
+  import { downloadAssets, isDownloading } from '$lib/stores/download';
+  import { fly, slide } from 'svelte/transition';
 </script>
 
 {#if $isDownloading}
-	<div
-		transition:fly={{ x: -100, duration: 350 }}
-		class="w-[315px] max-h-[270px] bg-immich-bg border rounded-2xl shadow-sm absolute bottom-10 left-2 p-4 z-[10000] text-sm"
-	>
-		<p class="text-gray-500 text-xs mb-2">DOWNLOADING</p>
-		<div class="max-h-[200px] my-2 overflow-y-auto mb-2 flex flex-col text-sm">
-			{#each Object.keys($downloadAssets) as fileName}
-				<div class="mb-2" transition:slide>
-					<p class="font-medium text-xs truncate">â–  {fileName}</p>
-					<div class="flex flex-row-reverse place-items-center gap-5">
-						<p>
-							<span class="text-immich-primary font-medium">{$downloadAssets[fileName]}</span>/100
-						</p>
-						<div class="w-full bg-gray-200 rounded-full h-[7px] dark:bg-gray-700">
-							<div
-								class="bg-immich-primary h-[7px] rounded-full"
-								style={`width: ${$downloadAssets[fileName]}%`}
-							/>
-						</div>
-					</div>
-				</div>
-			{/each}
-		</div>
-	</div>
+  <div
+    transition:fly={{ x: -100, duration: 350 }}
+    class="w-[315px] max-h-[270px] bg-immich-bg border rounded-2xl shadow-sm absolute bottom-10 left-2 p-4 z-[10000] text-sm"
+  >
+    <p class="text-gray-500 text-xs mb-2">DOWNLOADING</p>
+    <div class="max-h-[200px] my-2 overflow-y-auto mb-2 flex flex-col text-sm">
+      {#each Object.keys($downloadAssets) as fileName}
+        <div class="mb-2" transition:slide>
+          <p class="font-medium text-xs truncate">â–  {fileName}</p>
+          <div class="flex flex-row-reverse place-items-center gap-5">
+            <p>
+              <span class="text-immich-primary font-medium">{$downloadAssets[fileName]}</span>/100
+            </p>
+            <div class="w-full bg-gray-200 rounded-full h-[7px] dark:bg-gray-700">
+              <div class="bg-immich-primary h-[7px] rounded-full" style={`width: ${$downloadAssets[fileName]}%`} />
+            </div>
+          </div>
+        </div>
+      {/each}
+    </div>
+  </div>
 {/if}
diff --git a/web/src/lib/components/asset-viewer/intersection-observer.svelte b/web/src/lib/components/asset-viewer/intersection-observer.svelte
index b373c2d346..4b3d9b89a9 100644
--- a/web/src/lib/components/asset-viewer/intersection-observer.svelte
+++ b/web/src/lib/components/asset-viewer/intersection-observer.svelte
@@ -1,77 +1,77 @@
 <script lang="ts">
-	import { BucketPosition } from '$lib/models/asset-grid-state';
-	import { onMount } from 'svelte';
-	import { createEventDispatcher } from 'svelte';
+  import { BucketPosition } from '$lib/models/asset-grid-state';
+  import { onMount } from 'svelte';
+  import { createEventDispatcher } from 'svelte';
 
-	export let once = false;
-	export let top = 0;
-	export let bottom = 0;
-	export let left = 0;
-	export let right = 0;
-	export let root: HTMLElement | null = null;
+  export let once = false;
+  export let top = 0;
+  export let bottom = 0;
+  export let left = 0;
+  export let right = 0;
+  export let root: HTMLElement | null = null;
 
-	let intersecting = false;
-	let container: HTMLDivElement;
-	const dispatch = createEventDispatcher();
+  let intersecting = false;
+  let container: HTMLDivElement;
+  const dispatch = createEventDispatcher();
 
-	onMount(() => {
-		if (typeof IntersectionObserver !== 'undefined') {
-			const rootMargin = `${top}px ${right}px ${bottom}px ${left}px`;
-			const observer = new IntersectionObserver(
-				(entries) => {
-					intersecting = entries[0].isIntersecting;
-					if (!intersecting) {
-						dispatch('hidden', container);
-					}
+  onMount(() => {
+    if (typeof IntersectionObserver !== 'undefined') {
+      const rootMargin = `${top}px ${right}px ${bottom}px ${left}px`;
+      const observer = new IntersectionObserver(
+        (entries) => {
+          intersecting = entries[0].isIntersecting;
+          if (!intersecting) {
+            dispatch('hidden', container);
+          }
 
-					if (intersecting && once) {
-						observer.unobserve(container);
-					}
+          if (intersecting && once) {
+            observer.unobserve(container);
+          }
 
-					if (intersecting) {
-						let position: BucketPosition = BucketPosition.Visible;
-						if (entries[0].boundingClientRect.top + 50 > entries[0].intersectionRect.bottom) {
-							position = BucketPosition.Below;
-						} else if (entries[0].boundingClientRect.bottom < 0) {
-							position = BucketPosition.Above;
-						}
+          if (intersecting) {
+            let position: BucketPosition = BucketPosition.Visible;
+            if (entries[0].boundingClientRect.top + 50 > entries[0].intersectionRect.bottom) {
+              position = BucketPosition.Below;
+            } else if (entries[0].boundingClientRect.bottom < 0) {
+              position = BucketPosition.Above;
+            }
 
-						dispatch('intersected', {
-							container,
-							position
-						});
-					}
-				},
-				{
-					rootMargin,
-					root
-				}
-			);
+            dispatch('intersected', {
+              container,
+              position,
+            });
+          }
+        },
+        {
+          rootMargin,
+          root,
+        },
+      );
 
-			observer.observe(container);
-			return () => observer.unobserve(container);
-		}
+      observer.observe(container);
+      return () => observer.unobserve(container);
+    }
 
-		// The following is a fallback for older browsers
-		function handler() {
-			const bcr = container.getBoundingClientRect();
+    // The following is a fallback for older browsers
+    function handler() {
+      const bcr = container.getBoundingClientRect();
 
-			intersecting =
-				bcr.bottom + bottom > 0 &&
-				bcr.right + right > 0 &&
-				bcr.top - top < window.innerHeight &&
-				bcr.left - left < window.innerWidth;
+      intersecting =
+        bcr.bottom + bottom > 0 &&
+        bcr.right + right > 0 &&
+        bcr.top - top < window.innerHeight &&
+        bcr.left - left < window.innerWidth;
 
-			if (intersecting && once) {
-				window.removeEventListener('scroll', handler);
-			}
-		}
+      if (intersecting && once) {
+        window.removeEventListener('scroll', handler);
+      }
+    }
 
-		window.addEventListener('scroll', handler);
-		return () => window.removeEventListener('scroll', handler);
-	});
+    window.addEventListener('scroll', handler);
+    return () => window.removeEventListener('scroll', handler);
+  });
 </script>
 
 <div bind:this={container}>
-	<slot {intersecting} />
+  <slot {intersecting} />
 </div>
diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte
index e58fa655b2..bbf73c7342 100644
--- a/web/src/lib/components/asset-viewer/photo-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte
@@ -1,119 +1,113 @@
 <script lang="ts">
-	import { fade } from 'svelte/transition';
-	import { onMount } from 'svelte';
-	import LoadingSpinner from '../shared-components/loading-spinner.svelte';
-	import { api, AssetResponseDto } from '@api';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import { useZoomImageWheel } from '@zoom-image/svelte';
-	import { photoZoomState } from '$lib/stores/zoom-image.store';
+  import { fade } from 'svelte/transition';
+  import { onMount } from 'svelte';
+  import LoadingSpinner from '../shared-components/loading-spinner.svelte';
+  import { api, AssetResponseDto } from '@api';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import { useZoomImageWheel } from '@zoom-image/svelte';
+  import { photoZoomState } from '$lib/stores/zoom-image.store';
 
-	export let asset: AssetResponseDto;
-	export let publicSharedKey = '';
-	let imgElement: HTMLDivElement;
+  export let asset: AssetResponseDto;
+  export let publicSharedKey = '';
+  let imgElement: HTMLDivElement;
 
-	let assetData: string;
+  let assetData: string;
 
-	let copyImageToClipboard: (src: string) => Promise<Blob>;
-	let canCopyImagesToClipboard: () => boolean;
+  let copyImageToClipboard: (src: string) => Promise<Blob>;
+  let canCopyImagesToClipboard: () => boolean;
 
-	onMount(async () => {
-		// Import hack :( see https://github.com/vadimkorr/svelte-carousel/issues/27#issuecomment-851022295
-		// TODO: Move to regular import once the package correctly supports ESM.
-		const module = await import('copy-image-clipboard');
-		copyImageToClipboard = module.copyImageToClipboard;
-		canCopyImagesToClipboard = module.canCopyImagesToClipboard;
-	});
+  onMount(async () => {
+    // Import hack :( see https://github.com/vadimkorr/svelte-carousel/issues/27#issuecomment-851022295
+    // TODO: Move to regular import once the package correctly supports ESM.
+    const module = await import('copy-image-clipboard');
+    copyImageToClipboard = module.copyImageToClipboard;
+    canCopyImagesToClipboard = module.canCopyImagesToClipboard;
+  });
 
-	const loadAssetData = async () => {
-		try {
-			const { data } = await api.assetApi.serveFile(
-				{ id: asset.id, isThumb: false, isWeb: true, key: publicSharedKey },
-				{
-					responseType: 'blob'
-				}
-			);
+  const loadAssetData = async () => {
+    try {
+      const { data } = await api.assetApi.serveFile(
+        { id: asset.id, isThumb: false, isWeb: true, key: publicSharedKey },
+        {
+          responseType: 'blob',
+        },
+      );
 
-			if (!(data instanceof Blob)) {
-				return;
-			}
+      if (!(data instanceof Blob)) {
+        return;
+      }
 
-			assetData = URL.createObjectURL(data);
-			return assetData;
-		} catch {
-			// Do nothing
-		}
-	};
+      assetData = URL.createObjectURL(data);
+      return assetData;
+    } catch {
+      // Do nothing
+    }
+  };
 
-	const handleKeypress = async ({ metaKey, ctrlKey, key }: KeyboardEvent) => {
-		if ((metaKey || ctrlKey) && key === 'c') {
-			await doCopy();
-		}
-	};
+  const handleKeypress = async ({ metaKey, ctrlKey, key }: KeyboardEvent) => {
+    if ((metaKey || ctrlKey) && key === 'c') {
+      await doCopy();
+    }
+  };
 
-	const doCopy = async () => {
-		if (!canCopyImagesToClipboard()) {
-			return;
-		}
+  const doCopy = async () => {
+    if (!canCopyImagesToClipboard()) {
+      return;
+    }
 
-		try {
-			await copyImageToClipboard(assetData);
-			notificationController.show({
-				type: NotificationType.Info,
-				message: 'Copied image to clipboard.',
-				timeout: 3000
-			});
-		} catch (err) {
-			console.error('Error [photo-viewer]:', err);
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Copying image to clipboard failed.'
-			});
-		}
-	};
+    try {
+      await copyImageToClipboard(assetData);
+      notificationController.show({
+        type: NotificationType.Info,
+        message: 'Copied image to clipboard.',
+        timeout: 3000,
+      });
+    } catch (err) {
+      console.error('Error [photo-viewer]:', err);
+      notificationController.show({
+        type: NotificationType.Error,
+        message: 'Copying image to clipboard failed.',
+      });
+    }
+  };
 
-	const doZoomImage = async () => {
-		setZoomImageWheelState({
-			currentZoom: $zoomImageWheelState.currentZoom === 1 ? 2 : 1
-		});
-	};
+  const doZoomImage = async () => {
+    setZoomImageWheelState({
+      currentZoom: $zoomImageWheelState.currentZoom === 1 ? 2 : 1,
+    });
+  };
 
-	const {
-		createZoomImage: createZoomImageWheel,
-		zoomImageState: zoomImageWheelState,
-		setZoomImageState: setZoomImageWheelState
-	} = useZoomImageWheel();
+  const {
+    createZoomImage: createZoomImageWheel,
+    zoomImageState: zoomImageWheelState,
+    setZoomImageState: setZoomImageWheelState,
+  } = useZoomImageWheel();
 
-	zoomImageWheelState.subscribe((state) => {
-		photoZoomState.set(state);
-	});
+  zoomImageWheelState.subscribe((state) => {
+    photoZoomState.set(state);
+  });
 
-	$: if (imgElement) {
-		createZoomImageWheel(imgElement, {
-			wheelZoomRatio: 0.06
-		});
-	}
+  $: if (imgElement) {
+    createZoomImageWheel(imgElement, {
+      wheelZoomRatio: 0.06,
+    });
+  }
 </script>
 
 <svelte:window on:keydown={handleKeypress} on:copyImage={doCopy} on:zoomImage={doZoomImage} />
 
-<div
-	transition:fade={{ duration: 150 }}
-	class="flex place-items-center place-content-center h-full select-none"
->
-	{#await loadAssetData()}
-		<LoadingSpinner />
-	{:then assetData}
-		<div bind:this={imgElement} class="h-full w-full">
-			<img
-				transition:fade={{ duration: 150 }}
-				src={assetData}
-				alt={asset.id}
-				class="object-contain h-full w-full"
-				draggable="false"
-			/>
-		</div>
-	{/await}
+<div transition:fade={{ duration: 150 }} class="flex place-items-center place-content-center h-full select-none">
+  {#await loadAssetData()}
+    <LoadingSpinner />
+  {:then assetData}
+    <div bind:this={imgElement} class="h-full w-full">
+      <img
+        transition:fade={{ duration: 150 }}
+        src={assetData}
+        alt={asset.id}
+        class="object-contain h-full w-full"
+        draggable="false"
+      />
+    </div>
+  {/await}
 </div>
diff --git a/web/src/lib/components/asset-viewer/video-viewer.svelte b/web/src/lib/components/asset-viewer/video-viewer.svelte
index 801768dd8e..246ee97e19 100644
--- a/web/src/lib/components/asset-viewer/video-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/video-viewer.svelte
@@ -1,45 +1,42 @@
 <script lang="ts">
-	import { api } from '@api';
-	import { fade } from 'svelte/transition';
-	import { createEventDispatcher } from 'svelte';
-	import { videoViewerVolume } from '$lib/stores/preferences.store';
-	import LoadingSpinner from '../shared-components/loading-spinner.svelte';
+  import { api } from '@api';
+  import { fade } from 'svelte/transition';
+  import { createEventDispatcher } from 'svelte';
+  import { videoViewerVolume } from '$lib/stores/preferences.store';
+  import LoadingSpinner from '../shared-components/loading-spinner.svelte';
 
-	export let assetId: string;
-	export let publicSharedKey: string | undefined = undefined;
+  export let assetId: string;
+  export let publicSharedKey: string | undefined = undefined;
 
-	let isVideoLoading = true;
-	const dispatch = createEventDispatcher();
+  let isVideoLoading = true;
+  const dispatch = createEventDispatcher();
 
-	const handleCanPlay = (ev: Event & { currentTarget: HTMLVideoElement }) => {
-		const playerNode = ev.currentTarget;
+  const handleCanPlay = (ev: Event & { currentTarget: HTMLVideoElement }) => {
+    const playerNode = ev.currentTarget;
 
-		playerNode.muted = true;
-		playerNode.play();
-		playerNode.muted = false;
+    playerNode.muted = true;
+    playerNode.play();
+    playerNode.muted = false;
 
-		isVideoLoading = false;
-	};
+    isVideoLoading = false;
+  };
 </script>
 
-<div
-	transition:fade={{ duration: 150 }}
-	class="flex place-items-center place-content-center h-full select-none"
->
-	<video
-		controls
-		class="h-full object-contain"
-		on:canplay={handleCanPlay}
-		on:ended={() => dispatch('onVideoEnded')}
-		bind:volume={$videoViewerVolume}
-	>
-		<source src={api.getAssetFileUrl(assetId, false, true, publicSharedKey)} type="video/mp4" />
-		<track kind="captions" />
-	</video>
+<div transition:fade={{ duration: 150 }} class="flex place-items-center place-content-center h-full select-none">
+  <video
+    controls
+    class="h-full object-contain"
+    on:canplay={handleCanPlay}
+    on:ended={() => dispatch('onVideoEnded')}
+    bind:volume={$videoViewerVolume}
+  >
+    <source src={api.getAssetFileUrl(assetId, false, true, publicSharedKey)} type="video/mp4" />
+    <track kind="captions" />
+  </video>
 
-	{#if isVideoLoading}
-		<div class="absolute flex place-items-center place-content-center">
-			<LoadingSpinner />
-		</div>
-	{/if}
+  {#if isVideoLoading}
+    <div class="absolute flex place-items-center place-content-center">
+      <LoadingSpinner />
+    </div>
+  {/if}
 </div>
diff --git a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte
index 0acf5622e2..4b1eea5e52 100644
--- a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte
@@ -1,47 +1,47 @@
 <script lang="ts">
-	import { imageLoad } from '$lib/utils/image-load';
-	import { fade } from 'svelte/transition';
-	import { thumbHashToDataURL } from 'thumbhash';
-	import { Buffer } from 'buffer';
+  import { imageLoad } from '$lib/utils/image-load';
+  import { fade } from 'svelte/transition';
+  import { thumbHashToDataURL } from 'thumbhash';
+  import { Buffer } from 'buffer';
 
-	export let url: string;
-	export let altText: string;
-	export let heightStyle: string | undefined = undefined;
-	export let widthStyle: string;
-	export let thumbhash: string | null = null;
-	export let curve = false;
-	export let shadow = false;
-	export let circle = false;
+  export let url: string;
+  export let altText: string;
+  export let heightStyle: string | undefined = undefined;
+  export let widthStyle: string;
+  export let thumbhash: string | null = null;
+  export let curve = false;
+  export let shadow = false;
+  export let circle = false;
 
-	let complete = false;
+  let complete = false;
 </script>
 
 <img
-	style:width={widthStyle}
-	style:height={heightStyle}
-	src={url}
-	alt={altText}
-	class="object-cover transition-opacity duration-300"
-	class:rounded-lg={curve}
-	class:shadow-lg={shadow}
-	class:rounded-full={circle}
-	class:opacity-0={!thumbhash && !complete}
-	draggable="false"
-	use:imageLoad
-	on:image-load|once={() => (complete = true)}
+  style:width={widthStyle}
+  style:height={heightStyle}
+  src={url}
+  alt={altText}
+  class="object-cover transition-opacity duration-300"
+  class:rounded-lg={curve}
+  class:shadow-lg={shadow}
+  class:rounded-full={circle}
+  class:opacity-0={!thumbhash && !complete}
+  draggable="false"
+  use:imageLoad
+  on:image-load|once={() => (complete = true)}
 />
 
 {#if thumbhash && !complete}
-	<img
-		style:width={widthStyle}
-		style:height={heightStyle}
-		src={thumbHashToDataURL(Buffer.from(thumbhash, 'base64'))}
-		alt={altText}
-		class="absolute object-cover top-0"
-		class:rounded-lg={curve}
-		class:shadow-lg={shadow}
-		class:rounded-full={circle}
-		draggable="false"
-		out:fade={{ duration: 300 }}
-	/>
+  <img
+    style:width={widthStyle}
+    style:height={heightStyle}
+    src={thumbHashToDataURL(Buffer.from(thumbhash, 'base64'))}
+    alt={altText}
+    class="absolute object-cover top-0"
+    class:rounded-lg={curve}
+    class:shadow-lg={shadow}
+    class:rounded-full={circle}
+    draggable="false"
+    out:fade={{ duration: 300 }}
+  />
 {/if}
diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte
index 2e1ca9a357..f0946cfd16 100644
--- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte
@@ -1,158 +1,158 @@
 <script lang="ts">
-	import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
-	import { timeToSeconds } from '$lib/utils/time-to-seconds';
-	import { api, AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
-	import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
-	import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
-	import Heart from 'svelte-material-icons/Heart.svelte';
-	import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
-	import ImageThumbnail from './image-thumbnail.svelte';
-	import VideoThumbnail from './video-thumbnail.svelte';
-	import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
+  import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
+  import { timeToSeconds } from '$lib/utils/time-to-seconds';
+  import { api, AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
+  import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
+  import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
+  import Heart from 'svelte-material-icons/Heart.svelte';
+  import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
+  import ImageThumbnail from './image-thumbnail.svelte';
+  import VideoThumbnail from './video-thumbnail.svelte';
+  import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	export let asset: AssetResponseDto;
-	export let groupIndex = 0;
-	export let thumbnailSize: number | undefined = undefined;
-	export let thumbnailWidth: number | undefined = undefined;
-	export let thumbnailHeight: number | undefined = undefined;
-	export let format: ThumbnailFormat = ThumbnailFormat.Webp;
-	export let selected = false;
-	export let disabled = false;
-	export let readonly = false;
-	export let publicSharedKey: string | undefined = undefined;
-	export let showArchiveIcon = false;
+  export let asset: AssetResponseDto;
+  export let groupIndex = 0;
+  export let thumbnailSize: number | undefined = undefined;
+  export let thumbnailWidth: number | undefined = undefined;
+  export let thumbnailHeight: number | undefined = undefined;
+  export let format: ThumbnailFormat = ThumbnailFormat.Webp;
+  export let selected = false;
+  export let disabled = false;
+  export let readonly = false;
+  export let publicSharedKey: string | undefined = undefined;
+  export let showArchiveIcon = false;
 
-	let mouseOver = false;
+  let mouseOver = false;
 
-	$: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex });
+  $: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex });
 
-	$: [width, height] = (() => {
-		if (thumbnailSize) {
-			return [thumbnailSize, thumbnailSize];
-		}
+  $: [width, height] = (() => {
+    if (thumbnailSize) {
+      return [thumbnailSize, thumbnailSize];
+    }
 
-		if (thumbnailWidth && thumbnailHeight) {
-			return [thumbnailWidth, thumbnailHeight];
-		}
+    if (thumbnailWidth && thumbnailHeight) {
+      return [thumbnailWidth, thumbnailHeight];
+    }
 
-		return [235, 235];
-	})();
+    return [235, 235];
+  })();
 
-	const thumbnailClickedHandler = () => {
-		if (!disabled) {
-			dispatch('click', { asset });
-		}
-	};
+  const thumbnailClickedHandler = () => {
+    if (!disabled) {
+      dispatch('click', { asset });
+    }
+  };
 
-	const onIconClickedHandler = (e: MouseEvent) => {
-		e.stopPropagation();
-		if (!disabled) {
-			dispatch('select', { asset });
-		}
-	};
+  const onIconClickedHandler = (e: MouseEvent) => {
+    e.stopPropagation();
+    if (!disabled) {
+      dispatch('select', { asset });
+    }
+  };
 </script>
 
 <IntersectionObserver once={false} let:intersecting>
-	<div
-		style:width="{width}px"
-		style:height="{height}px"
-		class="relative group overflow-hidden {disabled
-			? 'bg-gray-300'
-			: 'bg-immich-primary/20 dark:bg-immich-dark-primary/20'}"
-		class:cursor-not-allowed={disabled}
-		class:hover:cursor-pointer={!disabled}
-		on:mouseenter={() => (mouseOver = true)}
-		on:mouseleave={() => (mouseOver = false)}
-		on:click={thumbnailClickedHandler}
-		on:keydown={thumbnailClickedHandler}
-	>
-		{#if intersecting}
-			<div class="absolute w-full h-full z-20">
-				<!-- Select asset button  -->
-				{#if !readonly}
-					<button
-						on:click={onIconClickedHandler}
-						class="absolute p-2 group-hover:block"
-						class:group-hover:block={!disabled}
-						class:hidden={!selected}
-						class:cursor-not-allowed={disabled}
-						role="checkbox"
-						aria-checked={selected}
-						{disabled}
-					>
-						{#if disabled}
-							<CheckCircle size="24" class="text-zinc-800" />
-						{:else if selected}
-							<CheckCircle size="24" class="text-immich-primary" />
-						{:else}
-							<CheckCircle size="24" class="text-white/80 hover:text-white" />
-						{/if}
-					</button>
-				{/if}
-			</div>
+  <div
+    style:width="{width}px"
+    style:height="{height}px"
+    class="relative group overflow-hidden {disabled
+      ? 'bg-gray-300'
+      : 'bg-immich-primary/20 dark:bg-immich-dark-primary/20'}"
+    class:cursor-not-allowed={disabled}
+    class:hover:cursor-pointer={!disabled}
+    on:mouseenter={() => (mouseOver = true)}
+    on:mouseleave={() => (mouseOver = false)}
+    on:click={thumbnailClickedHandler}
+    on:keydown={thumbnailClickedHandler}
+  >
+    {#if intersecting}
+      <div class="absolute w-full h-full z-20">
+        <!-- Select asset button  -->
+        {#if !readonly}
+          <button
+            on:click={onIconClickedHandler}
+            class="absolute p-2 group-hover:block"
+            class:group-hover:block={!disabled}
+            class:hidden={!selected}
+            class:cursor-not-allowed={disabled}
+            role="checkbox"
+            aria-checked={selected}
+            {disabled}
+          >
+            {#if disabled}
+              <CheckCircle size="24" class="text-zinc-800" />
+            {:else if selected}
+              <CheckCircle size="24" class="text-immich-primary" />
+            {:else}
+              <CheckCircle size="24" class="text-white/80 hover:text-white" />
+            {/if}
+          </button>
+        {/if}
+      </div>
 
-			<div
-				class="h-full w-full bg-gray-100 dark:bg-immich-dark-gray absolute select-none transition-transform"
-				class:scale-[0.85]={selected}
-			>
-				<!-- Gradient overlay on hover -->
-				<div
-					class="absolute w-full h-full bg-gradient-to-b from-black/25 via-[transparent_25%] opacity-0 group-hover:opacity-100 transition-opacity z-10"
-				/>
+      <div
+        class="h-full w-full bg-gray-100 dark:bg-immich-dark-gray absolute select-none transition-transform"
+        class:scale-[0.85]={selected}
+      >
+        <!-- Gradient overlay on hover -->
+        <div
+          class="absolute w-full h-full bg-gradient-to-b from-black/25 via-[transparent_25%] opacity-0 group-hover:opacity-100 transition-opacity z-10"
+        />
 
-				<!-- Favorite asset star -->
-				{#if asset.isFavorite && !publicSharedKey}
-					<div class="absolute bottom-2 left-2 z-10">
-						<Heart size="24" class="text-white" />
-					</div>
-				{/if}
+        <!-- Favorite asset star -->
+        {#if asset.isFavorite && !publicSharedKey}
+          <div class="absolute bottom-2 left-2 z-10">
+            <Heart size="24" class="text-white" />
+          </div>
+        {/if}
 
-				{#if showArchiveIcon && asset.isArchived}
-					<div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
-						<ArchiveArrowDownOutline size="24" class="text-white" />
-					</div>
-				{/if}
+        {#if showArchiveIcon && asset.isArchived}
+          <div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
+            <ArchiveArrowDownOutline size="24" class="text-white" />
+          </div>
+        {/if}
 
-				{#if asset.resized}
-					<ImageThumbnail
-						url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}
-						altText={asset.originalFileName}
-						widthStyle="{width}px"
-						heightStyle="{height}px"
-						thumbhash={asset.thumbhash}
-					/>
-				{:else}
-					<div class="w-full h-full p-4 flex items-center justify-center">
-						<ImageBrokenVariant size="48" />
-					</div>
-				{/if}
+        {#if asset.resized}
+          <ImageThumbnail
+            url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}
+            altText={asset.originalFileName}
+            widthStyle="{width}px"
+            heightStyle="{height}px"
+            thumbhash={asset.thumbhash}
+          />
+        {:else}
+          <div class="w-full h-full p-4 flex items-center justify-center">
+            <ImageBrokenVariant size="48" />
+          </div>
+        {/if}
 
-				{#if asset.type === AssetTypeEnum.Video}
-					<div class="absolute w-full h-full top-0">
-						<VideoThumbnail
-							url={api.getAssetFileUrl(asset.id, false, true, publicSharedKey)}
-							enablePlayback={mouseOver}
-							durationInSeconds={timeToSeconds(asset.duration)}
-						/>
-					</div>
-				{/if}
+        {#if asset.type === AssetTypeEnum.Video}
+          <div class="absolute w-full h-full top-0">
+            <VideoThumbnail
+              url={api.getAssetFileUrl(asset.id, false, true, publicSharedKey)}
+              enablePlayback={mouseOver}
+              durationInSeconds={timeToSeconds(asset.duration)}
+            />
+          </div>
+        {/if}
 
-				{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
-					<div class="absolute w-full h-full top-0">
-						<VideoThumbnail
-							url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true, publicSharedKey)}
-							pauseIcon={MotionPauseOutline}
-							playIcon={MotionPlayOutline}
-							showTime={false}
-							playbackOnIconHover
-						/>
-					</div>
-				{/if}
-			</div>
-		{/if}
-	</div>
+        {#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
+          <div class="absolute w-full h-full top-0">
+            <VideoThumbnail
+              url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true, publicSharedKey)}
+              pauseIcon={MotionPauseOutline}
+              playIcon={MotionPlayOutline}
+              showTime={false}
+              playbackOnIconHover
+            />
+          </div>
+        {/if}
+      </div>
+    {/if}
+  </div>
 </IntersectionObserver>
diff --git a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
index b8690794df..23e139f676 100644
--- a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
@@ -1,88 +1,86 @@
 <script lang="ts">
-	import { Duration } from 'luxon';
-	import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
-	import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
-	import AlertCircleOutline from 'svelte-material-icons/AlertCircleOutline.svelte';
-	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
+  import { Duration } from 'luxon';
+  import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
+  import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
+  import AlertCircleOutline from 'svelte-material-icons/AlertCircleOutline.svelte';
+  import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
 
-	export let url: string;
-	export let durationInSeconds = 0;
-	export let enablePlayback = false;
-	export let playbackOnIconHover = false;
-	export let showTime = true;
-	export let playIcon = PlayCircleOutline;
-	export let pauseIcon = PauseCircleOutline;
+  export let url: string;
+  export let durationInSeconds = 0;
+  export let enablePlayback = false;
+  export let playbackOnIconHover = false;
+  export let showTime = true;
+  export let playIcon = PlayCircleOutline;
+  export let pauseIcon = PauseCircleOutline;
 
-	let remainingSeconds = durationInSeconds;
-	let loading = true;
-	let error = false;
-	let player: HTMLVideoElement;
+  let remainingSeconds = durationInSeconds;
+  let loading = true;
+  let error = false;
+  let player: HTMLVideoElement;
 
-	$: if (!enablePlayback) {
-		// Reset remaining time when playback is disabled.
-		remainingSeconds = durationInSeconds;
+  $: if (!enablePlayback) {
+    // Reset remaining time when playback is disabled.
+    remainingSeconds = durationInSeconds;
 
-		if (player) {
-			// Cancel video buffering.
-			player.src = '';
-		}
-	}
+    if (player) {
+      // Cancel video buffering.
+      player.src = '';
+    }
+  }
 </script>
 
-<div
-	class="absolute right-0 top-0 text-white text-xs font-medium flex gap-1 place-items-center z-20"
->
-	{#if showTime}
-		<span class="pt-2">
-			{Duration.fromObject({ seconds: remainingSeconds }).toFormat('m:ss')}
-		</span>
-	{/if}
+<div class="absolute right-0 top-0 text-white text-xs font-medium flex gap-1 place-items-center z-20">
+  {#if showTime}
+    <span class="pt-2">
+      {Duration.fromObject({ seconds: remainingSeconds }).toFormat('m:ss')}
+    </span>
+  {/if}
 
-	<span
-		class="pt-2 pr-2"
-		on:mouseenter={() => {
-			if (playbackOnIconHover) {
-				enablePlayback = true;
-			}
-		}}
-		on:mouseleave={() => {
-			if (playbackOnIconHover) {
-				enablePlayback = false;
-			}
-		}}
-	>
-		{#if enablePlayback}
-			{#if loading}
-				<LoadingSpinner />
-			{:else if error}
-				<AlertCircleOutline size="24" class="text-red-600" />
-			{:else}
-				<svelte:component this={pauseIcon} size="24" />
-			{/if}
-		{:else}
-			<svelte:component this={playIcon} size="24" />
-		{/if}
-	</span>
+  <span
+    class="pt-2 pr-2"
+    on:mouseenter={() => {
+      if (playbackOnIconHover) {
+        enablePlayback = true;
+      }
+    }}
+    on:mouseleave={() => {
+      if (playbackOnIconHover) {
+        enablePlayback = false;
+      }
+    }}
+  >
+    {#if enablePlayback}
+      {#if loading}
+        <LoadingSpinner />
+      {:else if error}
+        <AlertCircleOutline size="24" class="text-red-600" />
+      {:else}
+        <svelte:component this={pauseIcon} size="24" />
+      {/if}
+    {:else}
+      <svelte:component this={playIcon} size="24" />
+    {/if}
+  </span>
 </div>
 
 {#if enablePlayback}
-	<video
-		bind:this={player}
-		class="w-full h-full object-cover"
-		muted
-		autoplay
-		src={url}
-		on:play={() => {
-			loading = false;
-			error = false;
-		}}
-		on:error={() => {
-			error = true;
-			loading = false;
-		}}
-		on:timeupdate={({ currentTarget }) => {
-			const remaining = currentTarget.duration - currentTarget.currentTime;
-			remainingSeconds = Math.min(Math.ceil(remaining), durationInSeconds);
-		}}
-	/>
+  <video
+    bind:this={player}
+    class="w-full h-full object-cover"
+    muted
+    autoplay
+    src={url}
+    on:play={() => {
+      loading = false;
+      error = false;
+    }}
+    on:error={() => {
+      error = true;
+      loading = false;
+    }}
+    on:timeupdate={({ currentTarget }) => {
+      const remaining = currentTarget.duration - currentTarget.currentTime;
+      remainingSeconds = Math.min(Math.ceil(remaining), durationInSeconds);
+    }}
+  />
 {/if}
diff --git a/web/src/lib/components/elements/badge.svelte b/web/src/lib/components/elements/badge.svelte
index 26296d9297..7305d2eb03 100644
--- a/web/src/lib/components/elements/badge.svelte
+++ b/web/src/lib/components/elements/badge.svelte
@@ -1,26 +1,24 @@
 <script lang="ts" context="module">
-	export type Color = 'primary' | 'secondary';
-	export type Rounded = false | true | 'full';
+  export type Color = 'primary' | 'secondary';
+  export type Rounded = false | true | 'full';
 </script>
 
 <script lang="ts">
-	export let color: Color = 'primary';
-	export let rounded: Rounded = true;
+  export let color: Color = 'primary';
+  export let rounded: Rounded = true;
 
-	const colorClasses: Record<Color, string> = {
-		primary:
-			'text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary',
-		secondary:
-			'text-immich-dark-bg dark:text-immich-gray dark:bg-gray-600 bg-gray-300 dark:text-immich-gray'
-	};
+  const colorClasses: Record<Color, string> = {
+    primary: 'text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary',
+    secondary: 'text-immich-dark-bg dark:text-immich-gray dark:bg-gray-600 bg-gray-300 dark:text-immich-gray',
+  };
 </script>
 
 <span
-	class="inline-block h-min whitespace-nowrap px-4 pt-[0.55em] pb-[0.55em] text-center align-baseline text-xs leading-none {colorClasses[
-		color
-	]}"
-	class:rounded-md={rounded === true}
-	class:rounded-full={rounded === 'full'}
+  class="inline-block h-min whitespace-nowrap px-4 pt-[0.55em] pb-[0.55em] text-center align-baseline text-xs leading-none {colorClasses[
+    color
+  ]}"
+  class:rounded-md={rounded === true}
+  class:rounded-full={rounded === 'full'}
 >
-	<slot />
+  <slot />
 </span>
diff --git a/web/src/lib/components/elements/buttons/button.svelte b/web/src/lib/components/elements/buttons/button.svelte
index b0c04e8738..8561cbc4c1 100644
--- a/web/src/lib/components/elements/buttons/button.svelte
+++ b/web/src/lib/components/elements/buttons/button.svelte
@@ -1,73 +1,73 @@
 <script lang="ts" context="module">
-	export type Type = 'button' | 'submit' | 'reset';
-	export type Color =
-		| 'primary'
-		| 'secondary'
-		| 'transparent-primary'
-		| 'light-red'
-		| 'red'
-		| 'green'
-		| 'gray'
-		| 'transparent-gray'
-		| 'dark-gray'
-		| 'overlay-primary';
-	export type Size = 'icon' | 'link' | 'sm' | 'base' | 'lg';
-	export type Rounded = 'lg' | '3xl' | 'full' | false;
-	export type Shadow = 'md' | false;
+  export type Type = 'button' | 'submit' | 'reset';
+  export type Color =
+    | 'primary'
+    | 'secondary'
+    | 'transparent-primary'
+    | 'light-red'
+    | 'red'
+    | 'green'
+    | 'gray'
+    | 'transparent-gray'
+    | 'dark-gray'
+    | 'overlay-primary';
+  export type Size = 'icon' | 'link' | 'sm' | 'base' | 'lg';
+  export type Rounded = 'lg' | '3xl' | 'full' | false;
+  export type Shadow = 'md' | false;
 </script>
 
 <script lang="ts">
-	export let type: Type = 'button';
-	export let color: Color = 'primary';
-	export let size: Size = 'base';
-	export let rounded: Rounded = '3xl';
-	export let shadow: Shadow = 'md';
-	export let disabled = false;
-	export let fullwidth = false;
-	export let border = false;
-	export let title: string | undefined = '';
+  export let type: Type = 'button';
+  export let color: Color = 'primary';
+  export let size: Size = 'base';
+  export let rounded: Rounded = '3xl';
+  export let shadow: Shadow = 'md';
+  export let disabled = false;
+  export let fullwidth = false;
+  export let border = false;
+  export let title: string | undefined = '';
 
-	const colorClasses: Record<Color, string> = {
-		primary:
-			'bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray enabled:dark:hover:bg-immich-dark-primary/80 enabled:hover:bg-immich-primary/90',
-		secondary:
-			'bg-gray-500 dark:bg-gray-200 text-white dark:text-immich-dark-gray enabled:hover:bg-gray-500/90 enabled:dark:hover:bg-gray-200/90',
-		'transparent-primary':
-			'text-gray-500 dark:text-immich-dark-primary enabled:hover:bg-gray-100 enabled:dark:hover:bg-gray-700',
-		'light-red': 'bg-[#F9DEDC] text-[#410E0B] enabled:hover:bg-red-50',
-		red: 'bg-red-500 text-white enabled:hover:bg-red-400',
-		green: 'bg-lime-600 text-white enabled:hover:bg-lime-500',
-		gray: 'bg-gray-500 dark:bg-gray-200 enabled:hover:bg-gray-500/75 enabled:dark:hover:bg-gray-200/80 text-white dark:text-immich-dark-gray',
-		'transparent-gray':
-			'dark:text-immich-dark-fg enabled:hover:bg-immich-primary/5 enabled:hover:text-gray-700 enabled:hover:dark:text-immich-dark-fg enabled:dark:hover:bg-immich-dark-primary/25',
-		'dark-gray':
-			'dark:border-immich-dark-gray dark:bg-gray-500 enabled:dark:hover:bg-immich-dark-primary/50 enabled:hover:bg-immich-primary/10 dark:text-white',
-		'overlay-primary': 'text-gray-500 enabled:hover:bg-gray-100'
-	};
+  const colorClasses: Record<Color, string> = {
+    primary:
+      'bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray enabled:dark:hover:bg-immich-dark-primary/80 enabled:hover:bg-immich-primary/90',
+    secondary:
+      'bg-gray-500 dark:bg-gray-200 text-white dark:text-immich-dark-gray enabled:hover:bg-gray-500/90 enabled:dark:hover:bg-gray-200/90',
+    'transparent-primary':
+      'text-gray-500 dark:text-immich-dark-primary enabled:hover:bg-gray-100 enabled:dark:hover:bg-gray-700',
+    'light-red': 'bg-[#F9DEDC] text-[#410E0B] enabled:hover:bg-red-50',
+    red: 'bg-red-500 text-white enabled:hover:bg-red-400',
+    green: 'bg-lime-600 text-white enabled:hover:bg-lime-500',
+    gray: 'bg-gray-500 dark:bg-gray-200 enabled:hover:bg-gray-500/75 enabled:dark:hover:bg-gray-200/80 text-white dark:text-immich-dark-gray',
+    'transparent-gray':
+      'dark:text-immich-dark-fg enabled:hover:bg-immich-primary/5 enabled:hover:text-gray-700 enabled:hover:dark:text-immich-dark-fg enabled:dark:hover:bg-immich-dark-primary/25',
+    'dark-gray':
+      'dark:border-immich-dark-gray dark:bg-gray-500 enabled:dark:hover:bg-immich-dark-primary/50 enabled:hover:bg-immich-primary/10 dark:text-white',
+    'overlay-primary': 'text-gray-500 enabled:hover:bg-gray-100',
+  };
 
-	const sizeClasses: Record<Size, string> = {
-		icon: 'p-2.5',
-		link: 'p-2 font-medium',
-		sm: 'px-4 py-2 text-sm font-medium',
-		base: 'px-6 py-3 font-medium',
-		lg: 'px-6 py-4 font-semibold'
-	};
+  const sizeClasses: Record<Size, string> = {
+    icon: 'p-2.5',
+    link: 'p-2 font-medium',
+    sm: 'px-4 py-2 text-sm font-medium',
+    base: 'px-6 py-3 font-medium',
+    lg: 'px-6 py-4 font-semibold',
+  };
 </script>
 
 <button
-	{type}
-	{disabled}
-	{title}
-	on:click
-	class="inline-flex justify-center items-center transition-colors disabled:cursor-not-allowed disabled:opacity-60 {colorClasses[
-		color
-	]} {sizeClasses[size]}"
-	class:rounded-lg={rounded === 'lg'}
-	class:rounded-3xl={rounded === '3xl'}
-	class:rounded-full={rounded === 'full'}
-	class:shadow-md={shadow === 'md'}
-	class:w-full={fullwidth}
-	class:border
+  {type}
+  {disabled}
+  {title}
+  on:click
+  class="inline-flex justify-center items-center transition-colors disabled:cursor-not-allowed disabled:opacity-60 {colorClasses[
+    color
+  ]} {sizeClasses[size]}"
+  class:rounded-lg={rounded === 'lg'}
+  class:rounded-3xl={rounded === '3xl'}
+  class:rounded-full={rounded === 'full'}
+  class:shadow-md={shadow === 'md'}
+  class:w-full={fullwidth}
+  class:border
 >
-	<slot />
+  <slot />
 </button>
diff --git a/web/src/lib/components/elements/buttons/circle-icon-button.svelte b/web/src/lib/components/elements/buttons/circle-icon-button.svelte
index 356c4079ef..e567e0d5bd 100644
--- a/web/src/lib/components/elements/buttons/circle-icon-button.svelte
+++ b/web/src/lib/components/elements/buttons/circle-icon-button.svelte
@@ -1,37 +1,37 @@
 <script lang="ts">
-	import type Icon from 'svelte-material-icons/AbTesting.svelte';
+  import type Icon from 'svelte-material-icons/AbTesting.svelte';
 
-	export let logo: typeof Icon;
-	export let backgroundColor = 'transparent';
-	export let hoverColor = '#e2e7e9';
-	export let size = '24';
-	export let title = '';
-	export let isOpacity = false;
-	export let forceDark = false;
-	export let hideMobile = false;
+  export let logo: typeof Icon;
+  export let backgroundColor = 'transparent';
+  export let hoverColor = '#e2e7e9';
+  export let size = '24';
+  export let title = '';
+  export let isOpacity = false;
+  export let forceDark = false;
+  export let hideMobile = false;
 </script>
 
 <button
-	{title}
-	style:background-color={backgroundColor}
-	style:--immich-icon-button-hover-color={hoverColor}
-	class:dark:text-immich-dark-fg={!forceDark}
-	class="rounded-full p-3 flex place-items-center place-content-center transition-all
+  {title}
+  style:background-color={backgroundColor}
+  style:--immich-icon-button-hover-color={hoverColor}
+  class:dark:text-immich-dark-fg={!forceDark}
+  class="rounded-full p-3 flex place-items-center place-content-center transition-all
 	{isOpacity ? 'hover:bg-immich-bg/30' : 'immich-circle-icon-button hover:dark:text-immich-dark-gray'}
   {forceDark && 'hover:text-black'}
   {hideMobile && 'hidden sm:flex'}"
-	on:click
+  on:click
 >
-	<svelte:component this={logo} {size} />
-	<slot />
+  <svelte:component this={logo} {size} />
+  <slot />
 </button>
 
 <style>
-	:root {
-		--immich-icon-button-hover-color: #d3d3d3;
-	}
+  :root {
+    --immich-icon-button-hover-color: #d3d3d3;
+  }
 
-	.immich-circle-icon-button:hover {
-		background-color: var(--immich-icon-button-hover-color) !important;
-	}
+  .immich-circle-icon-button:hover {
+    background-color: var(--immich-icon-button-hover-color) !important;
+  }
 </style>
diff --git a/web/src/lib/components/elements/buttons/icon-button.svelte b/web/src/lib/components/elements/buttons/icon-button.svelte
index ab46a55807..5602b731a2 100644
--- a/web/src/lib/components/elements/buttons/icon-button.svelte
+++ b/web/src/lib/components/elements/buttons/icon-button.svelte
@@ -1,14 +1,14 @@
 <script lang="ts" context="module">
-	export type Color = 'transparent-primary' | 'transparent-gray' | 'overlay-primary';
+  export type Color = 'transparent-primary' | 'transparent-gray' | 'overlay-primary';
 </script>
 
 <script lang="ts">
-	import Button from './button.svelte';
+  import Button from './button.svelte';
 
-	export let color: Color = 'transparent-primary';
-	export let title: string | undefined = undefined;
+  export let color: Color = 'transparent-primary';
+  export let title: string | undefined = undefined;
 </script>
 
 <Button size="icon" {color} {title} shadow={false} rounded="full" on:click>
-	<slot />
+  <slot />
 </Button>
diff --git a/web/src/lib/components/elements/buttons/link-button.svelte b/web/src/lib/components/elements/buttons/link-button.svelte
index fa45f384ee..0d0d369077 100644
--- a/web/src/lib/components/elements/buttons/link-button.svelte
+++ b/web/src/lib/components/elements/buttons/link-button.svelte
@@ -1,13 +1,13 @@
 <script lang="ts" context="module">
-	export type Color = 'transparent-primary' | 'transparent-gray';
+  export type Color = 'transparent-primary' | 'transparent-gray';
 </script>
 
 <script lang="ts">
-	import Button from './button.svelte';
+  import Button from './button.svelte';
 
-	export let color: Color = 'transparent-gray';
+  export let color: Color = 'transparent-gray';
 </script>
 
 <Button size="link" {color} shadow={false} rounded="lg" on:click>
-	<slot />
+  <slot />
 </Button>
diff --git a/web/src/lib/components/elements/dropdown.svelte b/web/src/lib/components/elements/dropdown.svelte
index 4afc4acbb3..38ed86fc95 100644
--- a/web/src/lib/components/elements/dropdown.svelte
+++ b/web/src/lib/components/elements/dropdown.svelte
@@ -1,62 +1,60 @@
 <script lang="ts">
-	import SwapVertical from 'svelte-material-icons/SwapVertical.svelte';
-	import Check from 'svelte-material-icons/Check.svelte';
-	import LinkButton from './buttons/link-button.svelte';
-	import { clickOutside } from '$lib/utils/click-outside';
-	import { fly } from 'svelte/transition';
+  import SwapVertical from 'svelte-material-icons/SwapVertical.svelte';
+  import Check from 'svelte-material-icons/Check.svelte';
+  import LinkButton from './buttons/link-button.svelte';
+  import { clickOutside } from '$lib/utils/click-outside';
+  import { fly } from 'svelte/transition';
 
-	export let options: string[] = [];
-	export let value = options[0];
+  export let options: string[] = [];
+  export let value = options[0];
 
-	let showMenu = false;
+  let showMenu = false;
 
-	const handleClickOutside = () => {
-		showMenu = false;
-	};
+  const handleClickOutside = () => {
+    showMenu = false;
+  };
 
-	const handleSelectOption = (index: number) => {
-		value = options[index];
-		showMenu = false;
-	};
+  const handleSelectOption = (index: number) => {
+    value = options[index];
+    showMenu = false;
+  };
 </script>
 
 <div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside}>
-	<!-- BUTTON TITLE -->
-	<LinkButton on:click={() => (showMenu = true)}>
-		<div class="flex place-items-center gap-2 text-sm">
-			<SwapVertical size="18" />
-			{value}
-		</div>
-	</LinkButton>
+  <!-- BUTTON TITLE -->
+  <LinkButton on:click={() => (showMenu = true)}>
+    <div class="flex place-items-center gap-2 text-sm">
+      <SwapVertical size="18" />
+      {value}
+    </div>
+  </LinkButton>
 
-	<!-- DROP DOWN MENU -->
-	{#if showMenu}
-		<div
-			transition:fly={{ y: -30, x: 30, duration: 200 }}
-			class="absolute top-5 right-0 min-w-[250px] bg-gray-100 dark:bg-gray-700 rounded-2xl py-4 shadow-lg dark:text-white text-black z-50 text-md flex flex-col"
-		>
-			{#each options as option, index (option)}
-				<button
-					class="hover:bg-gray-300 dark:hover:bg-gray-800 p-4 transition-all grid grid-cols-[20px,1fr] place-items-center gap-2"
-					on:click={() => handleSelectOption(index)}
-				>
-					{#if value == option}
-						<div class="text-immich-primary dark:text-immich-dark-primary font-medium">
-							<Check size="18" />
-						</div>
-						<p
-							class="justify-self-start text-immich-primary dark:text-immich-dark-primary font-medium"
-						>
-							{option}
-						</p>
-					{:else}
-						<div />
-						<p class="justify-self-start">
-							{option}
-						</p>
-					{/if}
-				</button>
-			{/each}
-		</div>
-	{/if}
+  <!-- DROP DOWN MENU -->
+  {#if showMenu}
+    <div
+      transition:fly={{ y: -30, x: 30, duration: 200 }}
+      class="absolute top-5 right-0 min-w-[250px] bg-gray-100 dark:bg-gray-700 rounded-2xl py-4 shadow-lg dark:text-white text-black z-50 text-md flex flex-col"
+    >
+      {#each options as option, index (option)}
+        <button
+          class="hover:bg-gray-300 dark:hover:bg-gray-800 p-4 transition-all grid grid-cols-[20px,1fr] place-items-center gap-2"
+          on:click={() => handleSelectOption(index)}
+        >
+          {#if value == option}
+            <div class="text-immich-primary dark:text-immich-dark-primary font-medium">
+              <Check size="18" />
+            </div>
+            <p class="justify-self-start text-immich-primary dark:text-immich-dark-primary font-medium">
+              {option}
+            </p>
+          {:else}
+            <div />
+            <p class="justify-self-start">
+              {option}
+            </p>
+          {/if}
+        </button>
+      {/each}
+    </div>
+  {/if}
 </div>
diff --git a/web/src/lib/components/faces-page/edit-name-input.svelte b/web/src/lib/components/faces-page/edit-name-input.svelte
index ba9a7eb5c4..f4fff8918d 100644
--- a/web/src/lib/components/faces-page/edit-name-input.svelte
+++ b/web/src/lib/components/faces-page/edit-name-input.svelte
@@ -1,46 +1,46 @@
 <script lang="ts">
-	import { PersonResponseDto, api } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import { clickOutside } from '$lib/utils/click-outside';
+  import { PersonResponseDto, api } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import { clickOutside } from '$lib/utils/click-outside';
 
-	export let person: PersonResponseDto;
-	let name = person.name;
+  export let person: PersonResponseDto;
+  let name = person.name;
 
-	const dispatch = createEventDispatcher<{
-		change: string;
-		cancel: void;
-	}>();
+  const dispatch = createEventDispatcher<{
+    change: string;
+    cancel: void;
+  }>();
 </script>
 
 <div
-	class="flex place-items-center max-w-lg rounded-lg border dark:border-transparent p-2 bg-gray-100 dark:bg-gray-700"
-	use:clickOutside
-	on:outclick={() => dispatch('cancel')}
+  class="flex place-items-center max-w-lg rounded-lg border dark:border-transparent p-2 bg-gray-100 dark:bg-gray-700"
+  use:clickOutside
+  on:outclick={() => dispatch('cancel')}
 >
-	<ImageThumbnail
-		circle
-		shadow
-		url={api.getPeopleThumbnailUrl(person.id)}
-		altText={person.name}
-		widthStyle="2rem"
-		heightStyle="2rem"
-	/>
-	<form
-		class="ml-4 flex justify-between w-full gap-16"
-		autocomplete="off"
-		on:submit|preventDefault={() => dispatch('change', name)}
-	>
-		<!-- svelte-ignore a11y-autofocus -->
-		<input
-			autofocus
-			class="gap-2 w-full bg-gray-100 dark:bg-gray-700 dark:text-white"
-			type="text"
-			placeholder="New name or nickname"
-			required
-			bind:value={name}
-		/>
-		<Button size="sm" type="submit">Done</Button>
-	</form>
+  <ImageThumbnail
+    circle
+    shadow
+    url={api.getPeopleThumbnailUrl(person.id)}
+    altText={person.name}
+    widthStyle="2rem"
+    heightStyle="2rem"
+  />
+  <form
+    class="ml-4 flex justify-between w-full gap-16"
+    autocomplete="off"
+    on:submit|preventDefault={() => dispatch('change', name)}
+  >
+    <!-- svelte-ignore a11y-autofocus -->
+    <input
+      autofocus
+      class="gap-2 w-full bg-gray-100 dark:bg-gray-700 dark:text-white"
+      type="text"
+      placeholder="New name or nickname"
+      required
+      bind:value={name}
+    />
+    <Button size="sm" type="submit">Done</Button>
+  </form>
 </div>
diff --git a/web/src/lib/components/forms/admin-registration-form.svelte b/web/src/lib/components/forms/admin-registration-form.svelte
index 49253d8ab1..353079ab27 100644
--- a/web/src/lib/components/forms/admin-registration-form.svelte
+++ b/web/src/lib/components/forms/admin-registration-form.svelte
@@ -1,123 +1,102 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import { AppRoute } from '$lib/constants';
-	import { api } from '@api';
-	import Button from '../elements/buttons/button.svelte';
+  import { goto } from '$app/navigation';
+  import { AppRoute } from '$lib/constants';
+  import { api } from '@api';
+  import Button from '../elements/buttons/button.svelte';
 
-	let error: string;
-	let password = '';
-	let confirmPassowrd = '';
-	let canRegister = false;
+  let error: string;
+  let password = '';
+  let confirmPassowrd = '';
+  let canRegister = false;
 
-	$: {
-		if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
-			error = 'Password does not match';
-			canRegister = false;
-		} else {
-			error = '';
-			canRegister = true;
-		}
-	}
+  $: {
+    if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
+      error = 'Password does not match';
+      canRegister = false;
+    } else {
+      error = '';
+      canRegister = true;
+    }
+  }
 
-	async function registerAdmin(event: SubmitEvent & { currentTarget: HTMLFormElement }) {
-		if (canRegister) {
-			error = '';
+  async function registerAdmin(event: SubmitEvent & { currentTarget: HTMLFormElement }) {
+    if (canRegister) {
+      error = '';
 
-			const form = new FormData(event.currentTarget);
+      const form = new FormData(event.currentTarget);
 
-			const email = form.get('email');
-			const password = form.get('password');
-			const firstName = form.get('firstName');
-			const lastName = form.get('lastName');
+      const email = form.get('email');
+      const password = form.get('password');
+      const firstName = form.get('firstName');
+      const lastName = form.get('lastName');
 
-			const { status } = await api.authenticationApi.adminSignUp({
-				signUpDto: {
-					email: String(email),
-					password: String(password),
-					firstName: String(firstName),
-					lastName: String(lastName)
-				}
-			});
+      const { status } = await api.authenticationApi.adminSignUp({
+        signUpDto: {
+          email: String(email),
+          password: String(password),
+          firstName: String(firstName),
+          lastName: String(lastName),
+        },
+      });
 
-			if (status === 201) {
-				goto(AppRoute.AUTH_LOGIN);
-				return;
-			} else {
-				error = 'Error create admin account';
-				return;
-			}
-		}
-	}
+      if (status === 201) {
+        goto(AppRoute.AUTH_LOGIN);
+        return;
+      } else {
+        error = 'Error create admin account';
+        return;
+      }
+    }
+  }
 </script>
 
 <form on:submit|preventDefault={registerAdmin} method="post" class="flex flex-col gap-5 mt-5">
-	<div class="flex flex-col gap-2">
-		<label class="immich-form-label" for="email">Admin Email</label>
-		<input
-			class="immich-form-input"
-			id="email"
-			name="email"
-			type="email"
-			autocomplete="email"
-			required
-		/>
-	</div>
+  <div class="flex flex-col gap-2">
+    <label class="immich-form-label" for="email">Admin Email</label>
+    <input class="immich-form-input" id="email" name="email" type="email" autocomplete="email" required />
+  </div>
 
-	<div class="flex flex-col gap-2">
-		<label class="immich-form-label" for="password">Admin Password</label>
-		<input
-			class="immich-form-input"
-			id="password"
-			name="password"
-			type="password"
-			autocomplete="new-password"
-			required
-			bind:value={password}
-		/>
-	</div>
+  <div class="flex flex-col gap-2">
+    <label class="immich-form-label" for="password">Admin Password</label>
+    <input
+      class="immich-form-input"
+      id="password"
+      name="password"
+      type="password"
+      autocomplete="new-password"
+      required
+      bind:value={password}
+    />
+  </div>
 
-	<div class="flex flex-col gap-2">
-		<label class="immich-form-label" for="confirmPassword">Confirm Admin Password</label>
-		<input
-			class="immich-form-input"
-			id="confirmPassword"
-			name="password"
-			type="password"
-			autocomplete="new-password"
-			required
-			bind:value={confirmPassowrd}
-		/>
-	</div>
+  <div class="flex flex-col gap-2">
+    <label class="immich-form-label" for="confirmPassword">Confirm Admin Password</label>
+    <input
+      class="immich-form-input"
+      id="confirmPassword"
+      name="password"
+      type="password"
+      autocomplete="new-password"
+      required
+      bind:value={confirmPassowrd}
+    />
+  </div>
 
-	<div class="flex flex-col gap-2">
-		<label class="immich-form-label" for="firstName">First Name</label>
-		<input
-			class="immich-form-input"
-			id="firstName"
-			name="firstName"
-			type="text"
-			autocomplete="given-name"
-			required
-		/>
-	</div>
+  <div class="flex flex-col gap-2">
+    <label class="immich-form-label" for="firstName">First Name</label>
+    <input class="immich-form-input" id="firstName" name="firstName" type="text" autocomplete="given-name" required />
+  </div>
 
-	<div class="flex flex-col gap-2">
-		<label class="immich-form-label" for="lastName">Last Name</label>
-		<input
-			class="immich-form-input"
-			id="lastName"
-			name="lastName"
-			type="text"
-			autocomplete="family-name"
-			required
-		/>
-	</div>
+  <div class="flex flex-col gap-2">
+    <label class="immich-form-label" for="lastName">Last Name</label>
+    <input class="immich-form-input" id="lastName" name="lastName" type="text" autocomplete="family-name" required />
+  </div>
 
-	{#if error}
-		<p class="text-red-400">{error}</p>
-	{/if}
+  {#if error}
+    <p class="text-red-400">{error}</p>
+  {/if}
 
-	<div class="my-5 flex w-full">
-		<Button type="submit" size="lg" fullwidth>Sign up</Button>
-	</div>
+  <div class="my-5 flex w-full">
+    <Button type="submit" size="lg" fullwidth>Sign up</Button>
+  </div>
 </form>
diff --git a/web/src/lib/components/forms/api-key-form.svelte b/web/src/lib/components/forms/api-key-form.svelte
index dad32d61be..10e6c2c563 100644
--- a/web/src/lib/components/forms/api-key-form.svelte
+++ b/web/src/lib/components/forms/api-key-form.svelte
@@ -1,49 +1,43 @@
 <script lang="ts">
-	import type { APIKeyResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import FullScreenModal from '../shared-components/full-screen-modal.svelte';
+  import type { APIKeyResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import FullScreenModal from '../shared-components/full-screen-modal.svelte';
 
-	export let apiKey: Partial<APIKeyResponseDto>;
-	export let title = 'API Key';
-	export let cancelText = 'Cancel';
-	export let submitText = 'Save';
+  export let apiKey: Partial<APIKeyResponseDto>;
+  export let title = 'API Key';
+  export let cancelText = 'Cancel';
+  export let submitText = 'Save';
 
-	const dispatch = createEventDispatcher();
-	const handleCancel = () => dispatch('cancel');
-	const handleSubmit = () => dispatch('submit', { ...apiKey, name: apiKey.name });
+  const dispatch = createEventDispatcher();
+  const handleCancel = () => dispatch('cancel');
+  const handleSubmit = () => dispatch('submit', { ...apiKey, name: apiKey.name });
 </script>
 
 <FullScreenModal on:clickOutside={() => handleCancel()}>
-	<div
-		class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
-	>
-		<div
-			class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
-		>
-			<KeyVariant size="4em" />
-			<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
-				{title}
-			</h1>
-		</div>
+  <div
+    class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
+  >
+    <div
+      class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
+    >
+      <KeyVariant size="4em" />
+      <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
+        {title}
+      </h1>
+    </div>
 
-		<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
-			<div class="m-4 flex flex-col gap-2">
-				<label class="immich-form-label" for="email">Name</label>
-				<input
-					class="immich-form-input"
-					id="name"
-					name="name"
-					type="text"
-					bind:value={apiKey.name}
-				/>
-			</div>
+    <form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
+      <div class="m-4 flex flex-col gap-2">
+        <label class="immich-form-label" for="email">Name</label>
+        <input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} />
+      </div>
 
-			<div class="flex w-full px-4 gap-4 mt-8">
-				<Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
-				<Button type="submit" fullwidth>{submitText}</Button>
-			</div>
-		</form>
-	</div>
+      <div class="flex w-full px-4 gap-4 mt-8">
+        <Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
+        <Button type="submit" fullwidth>{submitText}</Button>
+      </div>
+    </form>
+  </div>
 </FullScreenModal>
diff --git a/web/src/lib/components/forms/api-key-secret.svelte b/web/src/lib/components/forms/api-key-secret.svelte
index 1786215754..fd2f735855 100644
--- a/web/src/lib/components/forms/api-key-secret.svelte
+++ b/web/src/lib/components/forms/api-key-secret.svelte
@@ -1,70 +1,59 @@
 <script lang="ts">
-	import { createEventDispatcher, onMount } from 'svelte';
-	import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
-	import { handleError } from '../../utils/handle-error';
-	import FullScreenModal from '../shared-components/full-screen-modal.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import Button from '../elements/buttons/button.svelte';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
+  import { handleError } from '../../utils/handle-error';
+  import FullScreenModal from '../shared-components/full-screen-modal.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import Button from '../elements/buttons/button.svelte';
 
-	export let secret = '';
+  export let secret = '';
 
-	const dispatch = createEventDispatcher();
-	const handleDone = () => dispatch('done');
-	let canCopyImagesToClipboard = true;
+  const dispatch = createEventDispatcher();
+  const handleDone = () => dispatch('done');
+  let canCopyImagesToClipboard = true;
 
-	onMount(async () => {
-		const module = await import('copy-image-clipboard');
-		canCopyImagesToClipboard = module.canCopyImagesToClipboard();
-	});
-	const handleCopy = async () => {
-		try {
-			await navigator.clipboard.writeText(secret);
-			notificationController.show({
-				message: 'Copied to clipboard!',
-				type: NotificationType.Info
-			});
-		} catch (error) {
-			handleError(error, 'Unable to copy to clipboard');
-		}
-	};
+  onMount(async () => {
+    const module = await import('copy-image-clipboard');
+    canCopyImagesToClipboard = module.canCopyImagesToClipboard();
+  });
+  const handleCopy = async () => {
+    try {
+      await navigator.clipboard.writeText(secret);
+      notificationController.show({
+        message: 'Copied to clipboard!',
+        type: NotificationType.Info,
+      });
+    } catch (error) {
+      handleError(error, 'Unable to copy to clipboard');
+    }
+  };
 </script>
 
 <FullScreenModal>
-	<div
-		class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
-	>
-		<div
-			class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
-		>
-			<KeyVariant size="4em" />
-			<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
-				API Key
-			</h1>
+  <div
+    class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
+  >
+    <div
+      class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
+    >
+      <KeyVariant size="4em" />
+      <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">API Key</h1>
 
-			<p class="text-sm dark:text-immich-dark-fg">
-				This value will only be shown once. Please be sure to copy it before closing the window.
-			</p>
-		</div>
+      <p class="text-sm dark:text-immich-dark-fg">
+        This value will only be shown once. Please be sure to copy it before closing the window.
+      </p>
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<!-- <label class="immich-form-label" for="email">API Key</label> -->
-			<textarea
-				class="immich-form-input"
-				id="secret"
-				name="secret"
-				readonly={true}
-				value={secret}
-			/>
-		</div>
+    <div class="m-4 flex flex-col gap-2">
+      <!-- <label class="immich-form-label" for="email">API Key</label> -->
+      <textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret} />
+    </div>
 
-		<div class="flex w-full px-4 gap-4 mt-8">
-			{#if canCopyImagesToClipboard}
-				<Button on:click={() => handleCopy()} fullwidth>Copy to Clipboard</Button>
-			{/if}
-			<Button on:click={() => handleDone()} fullwidth>Done</Button>
-		</div>
-	</div>
+    <div class="flex w-full px-4 gap-4 mt-8">
+      {#if canCopyImagesToClipboard}
+        <Button on:click={() => handleCopy()} fullwidth>Copy to Clipboard</Button>
+      {/if}
+      <Button on:click={() => handleDone()} fullwidth>Done</Button>
+    </div>
+  </div>
 </FullScreenModal>
diff --git a/web/src/lib/components/forms/change-password-form.svelte b/web/src/lib/components/forms/change-password-form.svelte
index ba2e374287..35924241ca 100644
--- a/web/src/lib/components/forms/change-password-form.svelte
+++ b/web/src/lib/components/forms/change-password-form.svelte
@@ -1,86 +1,86 @@
 <script lang="ts">
-	import { api, UserResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import Button from '../elements/buttons/button.svelte';
+  import { api, UserResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import Button from '../elements/buttons/button.svelte';
 
-	export let user: UserResponseDto;
-	let error: string;
-	let success: string;
+  export let user: UserResponseDto;
+  let error: string;
+  let success: string;
 
-	let password = '';
-	let confirmPassowrd = '';
+  let password = '';
+  let confirmPassowrd = '';
 
-	let changeChagePassword = false;
+  let changeChagePassword = false;
 
-	$: {
-		if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
-			error = 'Password does not match';
-			changeChagePassword = false;
-		} else {
-			error = '';
-			changeChagePassword = true;
-		}
-	}
+  $: {
+    if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
+      error = 'Password does not match';
+      changeChagePassword = false;
+    } else {
+      error = '';
+      changeChagePassword = true;
+    }
+  }
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	async function changePassword() {
-		if (changeChagePassword) {
-			error = '';
+  async function changePassword() {
+    if (changeChagePassword) {
+      error = '';
 
-			const { status } = await api.userApi.updateUser({
-				updateUserDto: {
-					id: user.id,
-					password: String(password),
-					shouldChangePassword: false
-				}
-			});
+      const { status } = await api.userApi.updateUser({
+        updateUserDto: {
+          id: user.id,
+          password: String(password),
+          shouldChangePassword: false,
+        },
+      });
 
-			if (status === 200) {
-				dispatch('success');
-				return;
-			} else {
-				console.error('Error changing password');
-			}
-		}
-	}
+      if (status === 200) {
+        dispatch('success');
+        return;
+      } else {
+        console.error('Error changing password');
+      }
+    }
+  }
 </script>
 
 <form on:submit|preventDefault={changePassword} method="post" class="flex flex-col gap-5 mt-5">
-	<div class="flex flex-col gap-2">
-		<label class="immich-form-label" for="password">New Password</label>
-		<input
-			class="immich-form-input"
-			id="password"
-			name="password"
-			type="password"
-			autocomplete="new-password"
-			required
-			bind:value={password}
-		/>
-	</div>
+  <div class="flex flex-col gap-2">
+    <label class="immich-form-label" for="password">New Password</label>
+    <input
+      class="immich-form-input"
+      id="password"
+      name="password"
+      type="password"
+      autocomplete="new-password"
+      required
+      bind:value={password}
+    />
+  </div>
 
-	<div class="flex flex-col gap-2">
-		<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
-		<input
-			class="immich-form-input"
-			id="confirmPassword"
-			name="password"
-			type="password"
-			autocomplete="current-password"
-			required
-			bind:value={confirmPassowrd}
-		/>
-	</div>
+  <div class="flex flex-col gap-2">
+    <label class="immich-form-label" for="confirmPassword">Confirm Password</label>
+    <input
+      class="immich-form-input"
+      id="confirmPassword"
+      name="password"
+      type="password"
+      autocomplete="current-password"
+      required
+      bind:value={confirmPassowrd}
+    />
+  </div>
 
-	{#if error}
-		<p class="text-red-400 text-sm">{error}</p>
-	{/if}
+  {#if error}
+    <p class="text-red-400 text-sm">{error}</p>
+  {/if}
 
-	{#if success}
-		<p class="text-immich-primary text-sm">{success}</p>
-	{/if}
-	<div class="my-5 flex w-full">
-		<Button type="submit" size="lg" fullwidth>Change password</Button>
-	</div>
+  {#if success}
+    <p class="text-immich-primary text-sm">{success}</p>
+  {/if}
+  <div class="my-5 flex w-full">
+    <Button type="submit" size="lg" fullwidth>Change password</Button>
+  </div>
 </form>
diff --git a/web/src/lib/components/forms/create-user-form.svelte b/web/src/lib/components/forms/create-user-form.svelte
index bb2d198828..becced681b 100644
--- a/web/src/lib/components/forms/create-user-form.svelte
+++ b/web/src/lib/components/forms/create-user-form.svelte
@@ -1,150 +1,135 @@
 <script lang="ts">
-	import { api } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import ImmichLogo from '../shared-components/immich-logo.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import Button from '../elements/buttons/button.svelte';
+  import { api } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import ImmichLogo from '../shared-components/immich-logo.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import Button from '../elements/buttons/button.svelte';
 
-	let error: string;
-	let success: string;
+  let error: string;
+  let success: string;
 
-	let password = '';
-	let confirmPassowrd = '';
+  let password = '';
+  let confirmPassowrd = '';
 
-	let canCreateUser = false;
+  let canCreateUser = false;
 
-	let isCreatingUser = false;
+  let isCreatingUser = false;
 
-	$: {
-		if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
-			error = 'Password does not match';
-			canCreateUser = false;
-		} else {
-			error = '';
-			canCreateUser = true;
-		}
-	}
-	const dispatch = createEventDispatcher();
+  $: {
+    if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
+      error = 'Password does not match';
+      canCreateUser = false;
+    } else {
+      error = '';
+      canCreateUser = true;
+    }
+  }
+  const dispatch = createEventDispatcher();
 
-	async function registerUser(event: SubmitEvent) {
-		if (canCreateUser && !isCreatingUser) {
-			isCreatingUser = true;
+  async function registerUser(event: SubmitEvent) {
+    if (canCreateUser && !isCreatingUser) {
+      isCreatingUser = true;
 
-			error = '';
+      error = '';
 
-			const formElement = event.target as HTMLFormElement;
+      const formElement = event.target as HTMLFormElement;
 
-			const form = new FormData(formElement);
+      const form = new FormData(formElement);
 
-			const email = form.get('email');
-			const password = form.get('password');
-			const firstName = form.get('firstName');
-			const lastName = form.get('lastName');
+      const email = form.get('email');
+      const password = form.get('password');
+      const firstName = form.get('firstName');
+      const lastName = form.get('lastName');
 
-			try {
-				const { status } = await api.userApi.createUser({
-					createUserDto: {
-						email: String(email),
-						password: String(password),
-						firstName: String(firstName),
-						lastName: String(lastName)
-					}
-				});
+      try {
+        const { status } = await api.userApi.createUser({
+          createUserDto: {
+            email: String(email),
+            password: String(password),
+            firstName: String(firstName),
+            lastName: String(lastName),
+          },
+        });
 
-				if (status === 201) {
-					success = 'New user created';
+        if (status === 201) {
+          success = 'New user created';
 
-					dispatch('user-created');
+          dispatch('user-created');
 
-					isCreatingUser = false;
-					return;
-				} else {
-					error = 'Error create user account';
-					isCreatingUser = false;
-				}
-			} catch (e) {
-				error = 'Error create user account';
-				isCreatingUser = false;
+          isCreatingUser = false;
+          return;
+        } else {
+          error = 'Error create user account';
+          isCreatingUser = false;
+        }
+      } catch (e) {
+        error = 'Error create user account';
+        isCreatingUser = false;
 
-				console.log('[ERROR] registerUser', e);
+        console.log('[ERROR] registerUser', e);
 
-				notificationController.show({
-					message: `Error create new user, check console for more detail`,
-					type: NotificationType.Error
-				});
-			}
-		}
-	}
+        notificationController.show({
+          message: `Error create new user, check console for more detail`,
+          type: NotificationType.Error,
+        });
+      }
+    }
+  }
 </script>
 
 <div
-	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
+  class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
 >
-	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
-		<ImmichLogo class="text-center" height="100" width="100" />
-		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
-			Create new user
-		</h1>
-		<p
-			class="text-sm border rounded-md p-4 font-mono text-gray-600 dark:border-immich-dark-bg dark:text-gray-300"
-		>
-			Please provide your user with the password, they will have to change it on their first sign
-			in.
-		</p>
-	</div>
+  <div class="flex flex-col place-items-center place-content-center gap-4 px-4">
+    <ImmichLogo class="text-center" height="100" width="100" />
+    <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">Create new user</h1>
+    <p class="text-sm border rounded-md p-4 font-mono text-gray-600 dark:border-immich-dark-bg dark:text-gray-300">
+      Please provide your user with the password, they will have to change it on their first sign in.
+    </p>
+  </div>
 
-	<form on:submit|preventDefault={registerUser} autocomplete="off">
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="email">Email</label>
-			<input class="immich-form-input" id="email" name="email" type="email" required />
-		</div>
+  <form on:submit|preventDefault={registerUser} autocomplete="off">
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="email">Email</label>
+      <input class="immich-form-input" id="email" name="email" type="email" required />
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="password">Password</label>
-			<input
-				class="immich-form-input"
-				id="password"
-				name="password"
-				type="password"
-				required
-				bind:value={password}
-			/>
-		</div>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="password">Password</label>
+      <input class="immich-form-input" id="password" name="password" type="password" required bind:value={password} />
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
-			<input
-				class="immich-form-input"
-				id="confirmPassword"
-				name="password"
-				type="password"
-				required
-				bind:value={confirmPassowrd}
-			/>
-		</div>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="confirmPassword">Confirm Password</label>
+      <input
+        class="immich-form-input"
+        id="confirmPassword"
+        name="password"
+        type="password"
+        required
+        bind:value={confirmPassowrd}
+      />
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="firstName">First Name</label>
-			<input class="immich-form-input" id="firstName" name="firstName" type="text" required />
-		</div>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="firstName">First Name</label>
+      <input class="immich-form-input" id="firstName" name="firstName" type="text" required />
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="lastName">Last Name</label>
-			<input class="immich-form-input" id="lastName" name="lastName" type="text" required />
-		</div>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="lastName">Last Name</label>
+      <input class="immich-form-input" id="lastName" name="lastName" type="text" required />
+    </div>
 
-		{#if error}
-			<p class="text-red-400 ml-4 text-sm">{error}</p>
-		{/if}
+    {#if error}
+      <p class="text-red-400 ml-4 text-sm">{error}</p>
+    {/if}
 
-		{#if success}
-			<p class="text-immich-primary ml-4 text-sm">{success}</p>
-		{/if}
-		<div class="flex w-full p-4">
-			<Button type="submit" disabled={isCreatingUser} fullwidth>Create</Button>
-		</div>
-	</form>
+    {#if success}
+      <p class="text-immich-primary ml-4 text-sm">{success}</p>
+    {/if}
+    <div class="flex w-full p-4">
+      <Button type="submit" disabled={isCreatingUser} fullwidth>Create</Button>
+    </div>
+  </form>
 </div>
diff --git a/web/src/lib/components/forms/edit-user-form.svelte b/web/src/lib/components/forms/edit-user-form.svelte
index 6160901b79..8da224c8cb 100644
--- a/web/src/lib/components/forms/edit-user-form.svelte
+++ b/web/src/lib/components/forms/edit-user-form.svelte
@@ -1,187 +1,167 @@
 <script lang="ts">
-	import { api, UserResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import AccountEditOutline from 'svelte-material-icons/AccountEditOutline.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import Button from '../elements/buttons/button.svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
-	import { handleError } from '../../utils/handle-error';
+  import { api, UserResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import AccountEditOutline from 'svelte-material-icons/AccountEditOutline.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import Button from '../elements/buttons/button.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import { handleError } from '../../utils/handle-error';
 
-	export let user: UserResponseDto;
-	export let canResetPassword = true;
+  export let user: UserResponseDto;
+  export let canResetPassword = true;
 
-	let error: string;
-	let success: string;
+  let error: string;
+  let success: string;
 
-	let isShowResetPasswordConfirmation = false;
+  let isShowResetPasswordConfirmation = false;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	const editUser = async () => {
-		try {
-			const { id, email, firstName, lastName, storageLabel, externalPath } = user;
-			const { status } = await api.userApi.updateUser({
-				updateUserDto: {
-					id,
-					email,
-					firstName,
-					lastName,
-					storageLabel: storageLabel || '',
-					externalPath: externalPath || ''
-				}
-			});
+  const editUser = async () => {
+    try {
+      const { id, email, firstName, lastName, storageLabel, externalPath } = user;
+      const { status } = await api.userApi.updateUser({
+        updateUserDto: {
+          id,
+          email,
+          firstName,
+          lastName,
+          storageLabel: storageLabel || '',
+          externalPath: externalPath || '',
+        },
+      });
 
-			if (status === 200) {
-				dispatch('edit-success');
-			}
-		} catch (error) {
-			handleError(error, 'Unable to update user');
-		}
-	};
+      if (status === 200) {
+        dispatch('edit-success');
+      }
+    } catch (error) {
+      handleError(error, 'Unable to update user');
+    }
+  };
 
-	const resetPassword = async () => {
-		try {
-			const defaultPassword = 'password';
+  const resetPassword = async () => {
+    try {
+      const defaultPassword = 'password';
 
-			const { status } = await api.userApi.updateUser({
-				updateUserDto: {
-					id: user.id,
-					password: defaultPassword,
-					shouldChangePassword: true
-				}
-			});
+      const { status } = await api.userApi.updateUser({
+        updateUserDto: {
+          id: user.id,
+          password: defaultPassword,
+          shouldChangePassword: true,
+        },
+      });
 
-			if (status == 200) {
-				dispatch('reset-password-success');
-			}
-		} catch (e) {
-			console.error('Error reseting user password', e);
-			notificationController.show({
-				message: 'Error reseting user password, check console for more details',
-				type: NotificationType.Error
-			});
-		} finally {
-			isShowResetPasswordConfirmation = false;
-		}
-	};
+      if (status == 200) {
+        dispatch('reset-password-success');
+      }
+    } catch (e) {
+      console.error('Error reseting user password', e);
+      notificationController.show({
+        message: 'Error reseting user password, check console for more details',
+        type: NotificationType.Error,
+      });
+    } finally {
+      isShowResetPasswordConfirmation = false;
+    }
+  };
 </script>
 
 <div
-	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
+  class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
 >
-	<div
-		class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
-	>
-		<AccountEditOutline size="4em" />
-		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
-			Edit user
-		</h1>
-	</div>
+  <div
+    class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
+  >
+    <AccountEditOutline size="4em" />
+    <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">Edit user</h1>
+  </div>
 
-	<form on:submit|preventDefault={editUser} autocomplete="off">
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="email">Email</label>
-			<input
-				class="immich-form-input"
-				id="email"
-				name="email"
-				type="email"
-				bind:value={user.email}
-			/>
-		</div>
+  <form on:submit|preventDefault={editUser} autocomplete="off">
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="email">Email</label>
+      <input class="immich-form-input" id="email" name="email" type="email" bind:value={user.email} />
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="firstName">First Name</label>
-			<input
-				class="immich-form-input"
-				id="firstName"
-				name="firstName"
-				type="text"
-				required
-				bind:value={user.firstName}
-			/>
-		</div>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="firstName">First Name</label>
+      <input
+        class="immich-form-input"
+        id="firstName"
+        name="firstName"
+        type="text"
+        required
+        bind:value={user.firstName}
+      />
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="lastName">Last Name</label>
-			<input
-				class="immich-form-input"
-				id="lastName"
-				name="lastName"
-				type="text"
-				required
-				bind:value={user.lastName}
-			/>
-		</div>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="lastName">Last Name</label>
+      <input class="immich-form-input" id="lastName" name="lastName" type="text" required bind:value={user.lastName} />
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="storage-label">Storage Label</label>
-			<input
-				class="immich-form-input"
-				id="storage-label"
-				name="storage-label"
-				type="text"
-				bind:value={user.storageLabel}
-			/>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="storage-label">Storage Label</label>
+      <input
+        class="immich-form-input"
+        id="storage-label"
+        name="storage-label"
+        type="text"
+        bind:value={user.storageLabel}
+      />
 
-			<p>
-				Note: To apply the Storage Label to previously uploaded assets, run the
-				<a href="/admin/jobs-status" class="text-immich-primary dark:text-immich-dark-primary">
-					Storage Migration Job</a
-				>
-			</p>
-		</div>
+      <p>
+        Note: To apply the Storage Label to previously uploaded assets, run the
+        <a href="/admin/jobs-status" class="text-immich-primary dark:text-immich-dark-primary">
+          Storage Migration Job</a
+        >
+      </p>
+    </div>
 
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="external-path">External Path</label>
-			<input
-				class="immich-form-input"
-				id="external-path"
-				name="external-path"
-				type="text"
-				bind:value={user.externalPath}
-			/>
+    <div class="m-4 flex flex-col gap-2">
+      <label class="immich-form-label" for="external-path">External Path</label>
+      <input
+        class="immich-form-input"
+        id="external-path"
+        name="external-path"
+        type="text"
+        bind:value={user.externalPath}
+      />
 
-			<p>
-				Note: Absolute path of parent import directory. A user can only import files if they exist
-				at or under this path.
-			</p>
-		</div>
+      <p>
+        Note: Absolute path of parent import directory. A user can only import files if they exist at or under this
+        path.
+      </p>
+    </div>
 
-		{#if error}
-			<p class="text-red-400 ml-4 text-sm">{error}</p>
-		{/if}
+    {#if error}
+      <p class="text-red-400 ml-4 text-sm">{error}</p>
+    {/if}
 
-		{#if success}
-			<p class="text-immich-primary ml-4 text-sm">{success}</p>
-		{/if}
-		<div class="flex w-full px-4 gap-4 mt-8">
-			{#if canResetPassword}
-				<Button
-					color="light-red"
-					fullwidth
-					on:click={() => (isShowResetPasswordConfirmation = true)}>Reset password</Button
-				>
-			{/if}
-			<Button type="submit" fullwidth>Confirm</Button>
-		</div>
-	</form>
+    {#if success}
+      <p class="text-immich-primary ml-4 text-sm">{success}</p>
+    {/if}
+    <div class="flex w-full px-4 gap-4 mt-8">
+      {#if canResetPassword}
+        <Button color="light-red" fullwidth on:click={() => (isShowResetPasswordConfirmation = true)}
+          >Reset password</Button
+        >
+      {/if}
+      <Button type="submit" fullwidth>Confirm</Button>
+    </div>
+  </form>
 </div>
 
 {#if isShowResetPasswordConfirmation}
-	<ConfirmDialogue
-		title="Reset Password"
-		confirmText="Reset"
-		on:confirm={resetPassword}
-		on:cancel={() => (isShowResetPasswordConfirmation = false)}
-	>
-		<svelte:fragment slot="prompt">
-			<p>
-				Are you sure you want to reset <b>{user.firstName} {user.lastName}</b>'s password?
-			</p>
-		</svelte:fragment>
-	</ConfirmDialogue>
+  <ConfirmDialogue
+    title="Reset Password"
+    confirmText="Reset"
+    on:confirm={resetPassword}
+    on:cancel={() => (isShowResetPasswordConfirmation = false)}
+  >
+    <svelte:fragment slot="prompt">
+      <p>
+        Are you sure you want to reset <b>{user.firstName} {user.lastName}</b>'s password?
+      </p>
+    </svelte:fragment>
+  </ConfirmDialogue>
 {/if}
diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte
index c43be1c0d0..ed680b3424 100644
--- a/web/src/lib/components/forms/login-form.svelte
+++ b/web/src/lib/components/forms/login-form.svelte
@@ -1,166 +1,166 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
-	import { AppRoute } from '$lib/constants';
-	import { handleError } from '$lib/utils/handle-error';
-	import { api, oauth, OAuthConfigResponseDto } from '@api';
-	import { createEventDispatcher, onMount } from 'svelte';
-	import { fade } from 'svelte/transition';
-	import Button from '../elements/buttons/button.svelte';
+  import { goto } from '$app/navigation';
+  import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
+  import { AppRoute } from '$lib/constants';
+  import { handleError } from '$lib/utils/handle-error';
+  import { api, oauth, OAuthConfigResponseDto } from '@api';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import { fade } from 'svelte/transition';
+  import Button from '../elements/buttons/button.svelte';
 
-	let error: string;
-	let email = '';
-	let password = '';
-	let oauthError: string;
-	export let authConfig: OAuthConfigResponseDto;
-	let loading = false;
-	let oauthLoading = true;
+  let error: string;
+  let email = '';
+  let password = '';
+  let oauthError: string;
+  export let authConfig: OAuthConfigResponseDto;
+  let loading = false;
+  let oauthLoading = true;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	onMount(async () => {
-		if (oauth.isCallback(window.location)) {
-			try {
-				await oauth.login(window.location);
-				dispatch('success');
-				return;
-			} catch (e) {
-				console.error('Error [login-form] [oauth.callback]', e);
-				oauthError = 'Unable to complete OAuth login';
-			} finally {
-				oauthLoading = false;
-			}
-		}
+  onMount(async () => {
+    if (oauth.isCallback(window.location)) {
+      try {
+        await oauth.login(window.location);
+        dispatch('success');
+        return;
+      } catch (e) {
+        console.error('Error [login-form] [oauth.callback]', e);
+        oauthError = 'Unable to complete OAuth login';
+      } finally {
+        oauthLoading = false;
+      }
+    }
 
-		try {
-			const { data } = await oauth.getConfig(window.location);
-			authConfig = data;
+    try {
+      const { data } = await oauth.getConfig(window.location);
+      authConfig = data;
 
-			const { enabled, url, autoLaunch } = authConfig;
+      const { enabled, url, autoLaunch } = authConfig;
 
-			if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) {
-				await goto(`${AppRoute.AUTH_LOGIN}?autoLaunch=0`, { replaceState: true });
-				await goto(url);
-				return;
-			}
-		} catch (error) {
-			authConfig.passwordLoginEnabled = true;
-			handleError(error, 'Unable to connect!');
-		}
+      if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) {
+        await goto(`${AppRoute.AUTH_LOGIN}?autoLaunch=0`, { replaceState: true });
+        await goto(url);
+        return;
+      }
+    } catch (error) {
+      authConfig.passwordLoginEnabled = true;
+      handleError(error, 'Unable to connect!');
+    }
 
-		oauthLoading = false;
-	});
+    oauthLoading = false;
+  });
 
-	const login = async () => {
-		try {
-			error = '';
-			loading = true;
+  const login = async () => {
+    try {
+      error = '';
+      loading = true;
 
-			const { data } = await api.authenticationApi.login({
-				loginCredentialDto: {
-					email,
-					password
-				}
-			});
+      const { data } = await api.authenticationApi.login({
+        loginCredentialDto: {
+          email,
+          password,
+        },
+      });
 
-			if (!data.isAdmin && data.shouldChangePassword) {
-				dispatch('first-login');
-				return;
-			}
+      if (!data.isAdmin && data.shouldChangePassword) {
+        dispatch('first-login');
+        return;
+      }
 
-			dispatch('success');
-			return;
-		} catch (e) {
-			error = 'Incorrect email or password';
-			loading = false;
-			return;
-		}
-	};
+      dispatch('success');
+      return;
+    } catch (e) {
+      error = 'Incorrect email or password';
+      loading = false;
+      return;
+    }
+  };
 </script>
 
 {#if authConfig.passwordLoginEnabled}
-	<form on:submit|preventDefault={login} class="flex flex-col gap-5 mt-5">
-		{#if error}
-			<p class="text-red-400" transition:fade>
-				{error}
-			</p>
-		{/if}
+  <form on:submit|preventDefault={login} class="flex flex-col gap-5 mt-5">
+    {#if error}
+      <p class="text-red-400" transition:fade>
+        {error}
+      </p>
+    {/if}
 
-		<div class="flex flex-col gap-2">
-			<label class="immich-form-label" for="email">Email</label>
-			<input
-				class="immich-form-input"
-				id="email"
-				name="email"
-				type="email"
-				autocomplete="email"
-				bind:value={email}
-				required
-			/>
-		</div>
+    <div class="flex flex-col gap-2">
+      <label class="immich-form-label" for="email">Email</label>
+      <input
+        class="immich-form-input"
+        id="email"
+        name="email"
+        type="email"
+        autocomplete="email"
+        bind:value={email}
+        required
+      />
+    </div>
 
-		<div class="flex flex-col gap-2">
-			<label class="immich-form-label" for="password">Password</label>
-			<input
-				class="immich-form-input"
-				id="password"
-				name="password"
-				type="password"
-				autocomplete="current-password"
-				bind:value={password}
-				required
-			/>
-		</div>
+    <div class="flex flex-col gap-2">
+      <label class="immich-form-label" for="password">Password</label>
+      <input
+        class="immich-form-input"
+        id="password"
+        name="password"
+        type="password"
+        autocomplete="current-password"
+        bind:value={password}
+        required
+      />
+    </div>
 
-		<div class="my-5 flex w-full">
-			<Button type="submit" size="lg" fullwidth disabled={loading || oauthLoading}>
-				{#if loading}
-					<span class="h-6">
-						<LoadingSpinner />
-					</span>
-				{:else}
-					Login
-				{/if}
-			</Button>
-		</div>
-	</form>
+    <div class="my-5 flex w-full">
+      <Button type="submit" size="lg" fullwidth disabled={loading || oauthLoading}>
+        {#if loading}
+          <span class="h-6">
+            <LoadingSpinner />
+          </span>
+        {:else}
+          Login
+        {/if}
+      </Button>
+    </div>
+  </form>
 {/if}
 
 {#if authConfig.enabled}
-	{#if authConfig.passwordLoginEnabled}
-		<div class="inline-flex items-center justify-center w-full">
-			<hr class="w-3/4 h-px my-4 bg-gray-200 border-0 dark:bg-gray-600" />
-			<span
-				class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 left-1/2 dark:text-white bg-white dark:bg-immich-dark-gray"
-			>
-				or
-			</span>
-		</div>
-	{/if}
-	<div class="my-5 flex flex-col gap-5">
-		{#if oauthError}
-			<p class="text-red-400" transition:fade>{oauthError}</p>
-		{/if}
-		<a href={authConfig.url} class="flex w-full">
-			<Button
-				type="button"
-				disabled={loading || oauthLoading}
-				size="lg"
-				fullwidth
-				color={authConfig.passwordLoginEnabled ? 'secondary' : 'primary'}
-			>
-				{#if oauthLoading}
-					<span class="h-6">
-						<LoadingSpinner />
-					</span>
-				{:else}
-					{authConfig.buttonText || 'Login with OAuth'}
-				{/if}
-			</Button>
-		</a>
-	</div>
+  {#if authConfig.passwordLoginEnabled}
+    <div class="inline-flex items-center justify-center w-full">
+      <hr class="w-3/4 h-px my-4 bg-gray-200 border-0 dark:bg-gray-600" />
+      <span
+        class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 left-1/2 dark:text-white bg-white dark:bg-immich-dark-gray"
+      >
+        or
+      </span>
+    </div>
+  {/if}
+  <div class="my-5 flex flex-col gap-5">
+    {#if oauthError}
+      <p class="text-red-400" transition:fade>{oauthError}</p>
+    {/if}
+    <a href={authConfig.url} class="flex w-full">
+      <Button
+        type="button"
+        disabled={loading || oauthLoading}
+        size="lg"
+        fullwidth
+        color={authConfig.passwordLoginEnabled ? 'secondary' : 'primary'}
+      >
+        {#if oauthLoading}
+          <span class="h-6">
+            <LoadingSpinner />
+          </span>
+        {:else}
+          {authConfig.buttonText || 'Login with OAuth'}
+        {/if}
+      </Button>
+    </a>
+  </div>
 {/if}
 
 {#if !authConfig.enabled && !authConfig.passwordLoginEnabled}
-	<p class="text-center dark:text-immich-dark-fg p-4">Login has been disabled.</p>
+  <p class="text-center dark:text-immich-dark-fg p-4">Login has been disabled.</p>
 {/if}
diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte
index 1358a349ce..d625707a8c 100644
--- a/web/src/lib/components/layouts/user-page-layout.svelte
+++ b/web/src/lib/components/layouts/user-page-layout.svelte
@@ -1,44 +1,42 @@
 <script lang="ts">
-	import { openFileUploadDialog } from '$lib/utils/file-uploader';
-	import type { UserResponseDto } from '@api';
-	import NavigationBar from '../shared-components/navigation-bar/navigation-bar.svelte';
-	import SideBar from '../shared-components/side-bar/side-bar.svelte';
-	export let user: UserResponseDto;
-	export let hideNavbar = false;
-	export let showUploadButton = false;
-	export let title: string | undefined = undefined;
+  import { openFileUploadDialog } from '$lib/utils/file-uploader';
+  import type { UserResponseDto } from '@api';
+  import NavigationBar from '../shared-components/navigation-bar/navigation-bar.svelte';
+  import SideBar from '../shared-components/side-bar/side-bar.svelte';
+  export let user: UserResponseDto;
+  export let hideNavbar = false;
+  export let showUploadButton = false;
+  export let title: string | undefined = undefined;
 </script>
 
 <header>
-	{#if !hideNavbar}
-		<NavigationBar {user} {showUploadButton} on:uploadClicked={() => openFileUploadDialog()} />
-	{/if}
+  {#if !hideNavbar}
+    <NavigationBar {user} {showUploadButton} on:uploadClicked={() => openFileUploadDialog()} />
+  {/if}
 
-	<slot name="header" />
+  <slot name="header" />
 </header>
 
 <main
-	class="grid md:grid-cols-[theme(spacing.64)_auto] grid-cols-[theme(spacing.18)_auto] relative pt-[var(--navbar-height)] h-screen overflow-hidden bg-immich-bg dark:bg-immich-dark-bg"
+  class="grid md:grid-cols-[theme(spacing.64)_auto] grid-cols-[theme(spacing.18)_auto] relative pt-[var(--navbar-height)] h-screen overflow-hidden bg-immich-bg dark:bg-immich-dark-bg"
 >
-	<slot name="sidebar">
-		<SideBar />
-	</slot>
-	<slot name="content">
-		{#if title}
-			<section class="relative">
-				<div
-					class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16"
-				>
-					<p class="font-medium">{title}</p>
-					<slot name="buttons" />
-				</div>
+  <slot name="sidebar">
+    <SideBar />
+  </slot>
+  <slot name="content">
+    {#if title}
+      <section class="relative">
+        <div
+          class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16"
+        >
+          <p class="font-medium">{title}</p>
+          <slot name="buttons" />
+        </div>
 
-				<div
-					class="absolute overflow-y-auto top-16 h-[calc(100%-theme(spacing.16))] w-full immich-scrollbar p-4 pb-8"
-				>
-					<slot />
-				</div>
-			</section>
-		{/if}
-	</slot>
+        <div class="absolute overflow-y-auto top-16 h-[calc(100%-theme(spacing.16))] w-full immich-scrollbar p-4 pb-8">
+          <slot />
+        </div>
+      </section>
+    {/if}
+  </slot>
 </main>
diff --git a/web/src/lib/components/map-page/map-settings-modal.svelte b/web/src/lib/components/map-page/map-settings-modal.svelte
index cadf4ee073..786c210c8b 100644
--- a/web/src/lib/components/map-page/map-settings-modal.svelte
+++ b/web/src/lib/components/map-page/map-settings-modal.svelte
@@ -1,120 +1,113 @@
 <script lang="ts">
-	import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
-	import type { MapSettings } from '$lib/stores/preferences.store';
-	import { Duration } from 'luxon';
-	import { createEventDispatcher } from 'svelte';
-	import { fly } from 'svelte/transition';
-	import SettingSelect from '../admin-page/settings/setting-select.svelte';
-	import SettingSwitch from '../admin-page/settings/setting-switch.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import LinkButton from '../elements/buttons/link-button.svelte';
+  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
+  import type { MapSettings } from '$lib/stores/preferences.store';
+  import { Duration } from 'luxon';
+  import { createEventDispatcher } from 'svelte';
+  import { fly } from 'svelte/transition';
+  import SettingSelect from '../admin-page/settings/setting-select.svelte';
+  import SettingSwitch from '../admin-page/settings/setting-switch.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import LinkButton from '../elements/buttons/link-button.svelte';
 
-	export let settings: MapSettings;
-	let customDateRange = !!settings.dateAfter || !!settings.dateBefore;
+  export let settings: MapSettings;
+  let customDateRange = !!settings.dateAfter || !!settings.dateBefore;
 
-	const dispatch = createEventDispatcher<{
-		close: void;
-		save: MapSettings;
-	}>();
+  const dispatch = createEventDispatcher<{
+    close: void;
+    save: MapSettings;
+  }>();
 </script>
 
 <FullScreenModal on:clickOutside={() => dispatch('close')}>
-	<div
-		class="flex flex-col gap-8 border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-8 shadow-sm w-96 max-w-lg rounded-3xl"
-	>
-		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium self-center">
-			Map Settings
-		</h1>
+  <div
+    class="flex flex-col gap-8 border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-8 shadow-sm w-96 max-w-lg rounded-3xl"
+  >
+    <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium self-center">Map Settings</h1>
 
-		<form
-			on:submit|preventDefault={() => dispatch('save', settings)}
-			class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary"
-		>
-			<SettingSwitch title="Allow dark mode" bind:checked={settings.allowDarkMode} />
-			<SettingSwitch title="Only favorites" bind:checked={settings.onlyFavorites} />
-			{#if customDateRange}
-				<div in:fly={{ y: 10, duration: 200 }} class="flex flex-col gap-4">
-					<div class="flex justify-between items-center gap-8">
-						<label class="immich-form-label text-sm shrink-0" for="date-after">Date after</label>
-						<input
-							class="immich-form-input w-40"
-							type="date"
-							id="date-after"
-							max={settings.dateBefore}
-							bind:value={settings.dateAfter}
-						/>
-					</div>
-					<div class="flex justify-between items-center gap-8">
-						<label class="immich-form-label text-sm shrink-0" for="date-before">Date before</label>
-						<input
-							class="immich-form-input w-40"
-							type="date"
-							id="date-before"
-							bind:value={settings.dateBefore}
-						/>
-					</div>
-					<div class="flex justify-center text-xs">
-						<LinkButton
-							on:click={() => {
-								customDateRange = false;
-								settings.dateAfter = '';
-								settings.dateBefore = '';
-							}}
-						>
-							Remove custom date range
-						</LinkButton>
-					</div>
-				</div>
-			{:else}
-				<div in:fly={{ y: -10, duration: 200 }} class="flex flex-col gap-1">
-					<SettingSelect
-						label="Date range"
-						name="date-range"
-						bind:value={settings.relativeDate}
-						options={[
-							{
-								value: '',
-								text: 'All'
-							},
-							{
-								value: Duration.fromObject({ hours: 24 }).toISO() || '',
-								text: 'Past 24 hours'
-							},
-							{
-								value: Duration.fromObject({ days: 7 }).toISO() || '',
-								text: 'Past 7 days'
-							},
-							{
-								value: Duration.fromObject({ days: 30 }).toISO() || '',
-								text: 'Past 30 days'
-							},
-							{
-								value: Duration.fromObject({ years: 1 }).toISO() || '',
-								text: 'Past year'
-							},
-							{
-								value: Duration.fromObject({ years: 3 }).toISO() || '',
-								text: 'Past 3 years'
-							}
-						]}
-					/>
-					<div class="text-xs">
-						<LinkButton
-							on:click={() => {
-								customDateRange = true;
-								settings.relativeDate = '';
-							}}
-						>
-							Use custom date range instead
-						</LinkButton>
-					</div>
-				</div>
-			{/if}
+    <form
+      on:submit|preventDefault={() => dispatch('save', settings)}
+      class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary"
+    >
+      <SettingSwitch title="Allow dark mode" bind:checked={settings.allowDarkMode} />
+      <SettingSwitch title="Only favorites" bind:checked={settings.onlyFavorites} />
+      {#if customDateRange}
+        <div in:fly={{ y: 10, duration: 200 }} class="flex flex-col gap-4">
+          <div class="flex justify-between items-center gap-8">
+            <label class="immich-form-label text-sm shrink-0" for="date-after">Date after</label>
+            <input
+              class="immich-form-input w-40"
+              type="date"
+              id="date-after"
+              max={settings.dateBefore}
+              bind:value={settings.dateAfter}
+            />
+          </div>
+          <div class="flex justify-between items-center gap-8">
+            <label class="immich-form-label text-sm shrink-0" for="date-before">Date before</label>
+            <input class="immich-form-input w-40" type="date" id="date-before" bind:value={settings.dateBefore} />
+          </div>
+          <div class="flex justify-center text-xs">
+            <LinkButton
+              on:click={() => {
+                customDateRange = false;
+                settings.dateAfter = '';
+                settings.dateBefore = '';
+              }}
+            >
+              Remove custom date range
+            </LinkButton>
+          </div>
+        </div>
+      {:else}
+        <div in:fly={{ y: -10, duration: 200 }} class="flex flex-col gap-1">
+          <SettingSelect
+            label="Date range"
+            name="date-range"
+            bind:value={settings.relativeDate}
+            options={[
+              {
+                value: '',
+                text: 'All',
+              },
+              {
+                value: Duration.fromObject({ hours: 24 }).toISO() || '',
+                text: 'Past 24 hours',
+              },
+              {
+                value: Duration.fromObject({ days: 7 }).toISO() || '',
+                text: 'Past 7 days',
+              },
+              {
+                value: Duration.fromObject({ days: 30 }).toISO() || '',
+                text: 'Past 30 days',
+              },
+              {
+                value: Duration.fromObject({ years: 1 }).toISO() || '',
+                text: 'Past year',
+              },
+              {
+                value: Duration.fromObject({ years: 3 }).toISO() || '',
+                text: 'Past 3 years',
+              },
+            ]}
+          />
+          <div class="text-xs">
+            <LinkButton
+              on:click={() => {
+                customDateRange = true;
+                settings.relativeDate = '';
+              }}
+            >
+              Use custom date range instead
+            </LinkButton>
+          </div>
+        </div>
+      {/if}
 
-			<div class="flex w-full gap-4 mt-4">
-				<Button color="gray" size="sm" fullwidth on:click={() => dispatch('close')}>Cancel</Button>
-				<Button type="submit" size="sm" fullwidth>Save</Button>
-			</div>
-		</form>
-	</div>
+      <div class="flex w-full gap-4 mt-4">
+        <Button color="gray" size="sm" fullwidth on:click={() => dispatch('close')}>Cancel</Button>
+        <Button type="submit" size="sm" fullwidth>Save</Button>
+      </div>
+    </form>
+  </div>
 </FullScreenModal>
diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte
index 49444ef6b2..c9cbecc191 100644
--- a/web/src/lib/components/memory-page/memory-viewer.svelte
+++ b/web/src/lib/components/memory-page/memory-viewer.svelte
@@ -1,318 +1,282 @@
 <script lang="ts">
-	import { memoryStore } from '$lib/stores/memory.store';
-	import { DateTime } from 'luxon';
-	import { onMount } from 'svelte';
-	import { api } from '@api';
-	import { goto } from '$app/navigation';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import Play from 'svelte-material-icons/Play.svelte';
-	import Pause from 'svelte-material-icons/Pause.svelte';
-	import ChevronDown from 'svelte-material-icons/ChevronDown.svelte';
-	import ChevronUp from 'svelte-material-icons/ChevronUp.svelte';
-	import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
-	import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
-	import { AppRoute } from '$lib/constants';
-	import { page } from '$app/stores';
-	import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
-	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
-	import { fade } from 'svelte/transition';
-	import { tweened } from 'svelte/motion';
+  import { memoryStore } from '$lib/stores/memory.store';
+  import { DateTime } from 'luxon';
+  import { onMount } from 'svelte';
+  import { api } from '@api';
+  import { goto } from '$app/navigation';
+  import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
+  import Play from 'svelte-material-icons/Play.svelte';
+  import Pause from 'svelte-material-icons/Pause.svelte';
+  import ChevronDown from 'svelte-material-icons/ChevronDown.svelte';
+  import ChevronUp from 'svelte-material-icons/ChevronUp.svelte';
+  import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
+  import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
+  import { AppRoute } from '$lib/constants';
+  import { page } from '$app/stores';
+  import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
+  import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
+  import { fade } from 'svelte/transition';
+  import { tweened } from 'svelte/motion';
 
-	const parseIndex = (s: string | null, max: number | null) =>
-		Math.max(Math.min(parseInt(s ?? '') || 0, max ?? 0), 0);
+  const parseIndex = (s: string | null, max: number | null) => Math.max(Math.min(parseInt(s ?? '') || 0, max ?? 0), 0);
 
-	$: memoryIndex = parseIndex($page.url.searchParams.get('memory'), $memoryStore?.length - 1);
-	$: assetIndex = parseIndex($page.url.searchParams.get('asset'), currentMemory?.assets.length - 1);
+  $: memoryIndex = parseIndex($page.url.searchParams.get('memory'), $memoryStore?.length - 1);
+  $: assetIndex = parseIndex($page.url.searchParams.get('asset'), currentMemory?.assets.length - 1);
 
-	$: previousMemory = $memoryStore?.[memoryIndex - 1];
-	$: currentMemory = $memoryStore?.[memoryIndex];
-	$: nextMemory = $memoryStore?.[memoryIndex + 1];
+  $: previousMemory = $memoryStore?.[memoryIndex - 1];
+  $: currentMemory = $memoryStore?.[memoryIndex];
+  $: nextMemory = $memoryStore?.[memoryIndex + 1];
 
-	$: previousAsset = currentMemory?.assets[assetIndex - 1];
-	$: currentAsset = currentMemory?.assets[assetIndex];
-	$: nextAsset = currentMemory?.assets[assetIndex + 1];
+  $: previousAsset = currentMemory?.assets[assetIndex - 1];
+  $: currentAsset = currentMemory?.assets[assetIndex];
+  $: nextAsset = currentMemory?.assets[assetIndex + 1];
 
-	$: canGoForward = !!(nextMemory || nextAsset);
-	$: canGoBack = !!(previousMemory || previousAsset);
+  $: canGoForward = !!(nextMemory || nextAsset);
+  $: canGoBack = !!(previousMemory || previousAsset);
 
-	const toNextMemory = () => goto(`?memory=${memoryIndex + 1}`);
-	const toPreviousMemory = () => goto(`?memory=${memoryIndex - 1}`);
+  const toNextMemory = () => goto(`?memory=${memoryIndex + 1}`);
+  const toPreviousMemory = () => goto(`?memory=${memoryIndex - 1}`);
 
-	const toNextAsset = () => goto(`?memory=${memoryIndex}&asset=${assetIndex + 1}`);
-	const toPreviousAsset = () => goto(`?memory=${memoryIndex}&asset=${assetIndex - 1}`);
+  const toNextAsset = () => goto(`?memory=${memoryIndex}&asset=${assetIndex + 1}`);
+  const toPreviousAsset = () => goto(`?memory=${memoryIndex}&asset=${assetIndex - 1}`);
 
-	const toNext = () => (nextAsset ? toNextAsset() : toNextMemory());
-	const toPrevious = () => (previousAsset ? toPreviousAsset() : toPreviousMemory());
+  const toNext = () => (nextAsset ? toNextAsset() : toNextMemory());
+  const toPrevious = () => (previousAsset ? toPreviousAsset() : toPreviousMemory());
 
-	const progress = tweened<number>(0, {
-		duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0)
-	});
+  const progress = tweened<number>(0, {
+    duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
+  });
 
-	const play = () => progress.set(1);
-	const pause = () => progress.set($progress);
+  const play = () => progress.set(1);
+  const pause = () => progress.set($progress);
 
-	let resetPromise = Promise.resolve();
-	const reset = () => (resetPromise = progress.set(0));
+  let resetPromise = Promise.resolve();
+  const reset = () => (resetPromise = progress.set(0));
 
-	let paused = false;
+  let paused = false;
 
-	// Play or pause progress when the paused state changes.
-	$: paused ? pause() : play();
+  // Play or pause progress when the paused state changes.
+  $: paused ? pause() : play();
 
-	// Progress should be paused when it's no longer possible to advance.
-	$: paused ||= !canGoForward || galleryInView;
+  // Progress should be paused when it's no longer possible to advance.
+  $: paused ||= !canGoForward || galleryInView;
 
-	// Advance to the next asset or memory when progress is complete.
-	$: $progress === 1 && toNext();
+  // Advance to the next asset or memory when progress is complete.
+  $: $progress === 1 && toNext();
 
-	// Progress should be resumed when reset and not paused.
-	$: !$progress && !paused && play();
+  // Progress should be resumed when reset and not paused.
+  $: !$progress && !paused && play();
 
-	// Progress should be reset when the current memory or asset changes.
-	$: memoryIndex, assetIndex, reset();
+  // Progress should be reset when the current memory or asset changes.
+  $: memoryIndex, assetIndex, reset();
 
-	const handleKeyDown = (e: KeyboardEvent) => {
-		if (e.key === 'ArrowRight' && canGoForward) {
-			e.preventDefault();
-			toNext();
-		} else if (e.key === 'ArrowLeft' && canGoBack) {
-			e.preventDefault();
-			toPrevious();
-		} else if (e.key === 'Escape') {
-			e.preventDefault();
-			goto(AppRoute.PHOTOS);
-		}
-	};
+  const handleKeyDown = (e: KeyboardEvent) => {
+    if (e.key === 'ArrowRight' && canGoForward) {
+      e.preventDefault();
+      toNext();
+    } else if (e.key === 'ArrowLeft' && canGoBack) {
+      e.preventDefault();
+      toPrevious();
+    } else if (e.key === 'Escape') {
+      e.preventDefault();
+      goto(AppRoute.PHOTOS);
+    }
+  };
 
-	onMount(async () => {
-		if (!$memoryStore) {
-			const { data } = await api.assetApi.getMemoryLane({
-				timestamp: DateTime.local().startOf('day').toISO() || ''
-			});
-			$memoryStore = data;
-		}
-	});
+  onMount(async () => {
+    if (!$memoryStore) {
+      const { data } = await api.assetApi.getMemoryLane({
+        timestamp: DateTime.local().startOf('day').toISO() || '',
+      });
+      $memoryStore = data;
+    }
+  });
 
-	let memoryGallery: HTMLElement;
-	let memoryWrapper: HTMLElement;
-	let galleryInView = false;
+  let memoryGallery: HTMLElement;
+  let memoryWrapper: HTMLElement;
+  let galleryInView = false;
 </script>
 
 <svelte:window on:keydown={handleKeyDown} />
 
 <section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
-	{#if currentMemory}
-		<ControlAppBar on:close-button-click={() => goto(AppRoute.PHOTOS)} forceDark>
-			<svelte:fragment slot="leading">
-				<p class="text-lg">
-					{currentMemory.title}
-				</p>
-			</svelte:fragment>
+  {#if currentMemory}
+    <ControlAppBar on:close-button-click={() => goto(AppRoute.PHOTOS)} forceDark>
+      <svelte:fragment slot="leading">
+        <p class="text-lg">
+          {currentMemory.title}
+        </p>
+      </svelte:fragment>
 
-			{#if !galleryInView}
-				<div class="flex place-items-center place-content-center overflow-hidden gap-2">
-					<CircleIconButton
-						logo={paused ? Play : Pause}
-						forceDark
-						on:click={() => (paused = !paused)}
-					/>
+      {#if !galleryInView}
+        <div class="flex place-items-center place-content-center overflow-hidden gap-2">
+          <CircleIconButton logo={paused ? Play : Pause} forceDark on:click={() => (paused = !paused)} />
 
-					{#each currentMemory.assets as _, i}
-						<button
-							class="relative w-full py-2"
-							on:click={() => goto(`?memory=${memoryIndex}&asset=${i}`)}
-						>
-							<span class="absolute left-0 w-full h-[2px] bg-gray-500" />
-							{#await resetPromise}
-								<span
-									class="absolute left-0 h-[2px] bg-white"
-									style:width={`${i < assetIndex ? 100 : 0}%`}
-								/>
-							{:then}
-								<span
-									class="absolute left-0 h-[2px] bg-white"
-									style:width={`${i < assetIndex ? 100 : i > assetIndex ? 0 : $progress * 100}%`}
-								/>
-							{/await}
-						</button>
-					{/each}
+          {#each currentMemory.assets as _, i}
+            <button class="relative w-full py-2" on:click={() => goto(`?memory=${memoryIndex}&asset=${i}`)}>
+              <span class="absolute left-0 w-full h-[2px] bg-gray-500" />
+              {#await resetPromise}
+                <span class="absolute left-0 h-[2px] bg-white" style:width={`${i < assetIndex ? 100 : 0}%`} />
+              {:then}
+                <span
+                  class="absolute left-0 h-[2px] bg-white"
+                  style:width={`${i < assetIndex ? 100 : i > assetIndex ? 0 : $progress * 100}%`}
+                />
+              {/await}
+            </button>
+          {/each}
 
-					<div>
-						<p class="text-small">
-							{assetIndex + 1}/{currentMemory.assets.length}
-						</p>
-					</div>
-				</div>
-			{/if}
-		</ControlAppBar>
+          <div>
+            <p class="text-small">
+              {assetIndex + 1}/{currentMemory.assets.length}
+            </p>
+          </div>
+        </div>
+      {/if}
+    </ControlAppBar>
 
-		{#if galleryInView}
-			<div
-				class="sticky top-20 flex place-content-center place-items-center z-30 transition-opacity"
-				class:opacity-0={!galleryInView}
-				class:opacity-100={galleryInView}
-			>
-				<button
-					on:click={() => memoryWrapper.scrollIntoView({ behavior: 'smooth' })}
-					disabled={!galleryInView}
-				>
-					<CircleIconButton logo={ChevronUp} backgroundColor="white" forceDark />
-				</button>
-			</div>
-		{/if}
-		<!-- Viewer -->
-		<section class="pt-20 overflow-hidden">
-			<div
-				class="flex w-[300%] h-[calc(100vh_-_180px)] items-center justify-center box-border ml-[-100%] gap-10 overflow-hidden"
-			>
-				<!-- PREVIOUS MEMORY -->
-				<div
-					class="rounded-2xl w-[20vw] h-1/2"
-					class:opacity-25={previousMemory}
-					class:opacity-0={!previousMemory}
-					class:hover:opacity-70={previousMemory}
-				>
-					<button
-						class="rounded-2xl h-full w-full relative"
-						disabled={!previousMemory}
-						on:click={toPreviousMemory}
-					>
-						<img
-							class="rounded-2xl h-full w-full object-cover"
-							src={previousMemory
-								? api.getAssetThumbnailUrl(previousMemory.assets[0].id, 'JPEG')
-								: noThumbnailUrl}
-							alt=""
-							draggable="false"
-						/>
+    {#if galleryInView}
+      <div
+        class="sticky top-20 flex place-content-center place-items-center z-30 transition-opacity"
+        class:opacity-0={!galleryInView}
+        class:opacity-100={galleryInView}
+      >
+        <button on:click={() => memoryWrapper.scrollIntoView({ behavior: 'smooth' })} disabled={!galleryInView}>
+          <CircleIconButton logo={ChevronUp} backgroundColor="white" forceDark />
+        </button>
+      </div>
+    {/if}
+    <!-- Viewer -->
+    <section class="pt-20 overflow-hidden">
+      <div
+        class="flex w-[300%] h-[calc(100vh_-_180px)] items-center justify-center box-border ml-[-100%] gap-10 overflow-hidden"
+      >
+        <!-- PREVIOUS MEMORY -->
+        <div
+          class="rounded-2xl w-[20vw] h-1/2"
+          class:opacity-25={previousMemory}
+          class:opacity-0={!previousMemory}
+          class:hover:opacity-70={previousMemory}
+        >
+          <button class="rounded-2xl h-full w-full relative" disabled={!previousMemory} on:click={toPreviousMemory}>
+            <img
+              class="rounded-2xl h-full w-full object-cover"
+              src={previousMemory ? api.getAssetThumbnailUrl(previousMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
+              alt=""
+              draggable="false"
+            />
 
-						{#if previousMemory}
-							<div class="absolute right-4 bottom-4 text-white text-left">
-								<p class="font-semibold text-xs text-gray-200">PREVIOUS</p>
-								<p class="text-xl">{previousMemory.title}</p>
-							</div>
-						{/if}
-					</button>
-				</div>
+            {#if previousMemory}
+              <div class="absolute right-4 bottom-4 text-white text-left">
+                <p class="font-semibold text-xs text-gray-200">PREVIOUS</p>
+                <p class="text-xl">{previousMemory.title}</p>
+              </div>
+            {/if}
+          </button>
+        </div>
 
-				<!-- CURRENT MEMORY -->
-				<div
-					class="main-view rounded-2xl h-full relative w-[70vw] bg-black flex place-items-center place-content-center"
-				>
-					<div class="bg-black w-full h-full rounded-2xl">
-						<!-- CONTROL BUTTONS -->
-						<div class="absolute h-full flex justify-between w-full">
-							<div class="flex h-full flex-col place-content-center place-items-center ml-4">
-								<div class="inline-block">
-									{#if canGoBack}
-										<CircleIconButton
-											logo={ChevronLeft}
-											backgroundColor="#202123"
-											on:click={toPrevious}
-										/>
-									{/if}
-								</div>
-							</div>
-							<div class="flex h-full flex-col place-content-center place-items-center mr-4">
-								<div class="inline-block">
-									{#if canGoForward}
-										<CircleIconButton
-											logo={ChevronRight}
-											backgroundColor="#202123"
-											on:click={toNext}
-										/>
-									{/if}
-								</div>
-							</div>
-						</div>
+        <!-- CURRENT MEMORY -->
+        <div
+          class="main-view rounded-2xl h-full relative w-[70vw] bg-black flex place-items-center place-content-center"
+        >
+          <div class="bg-black w-full h-full rounded-2xl">
+            <!-- CONTROL BUTTONS -->
+            <div class="absolute h-full flex justify-between w-full">
+              <div class="flex h-full flex-col place-content-center place-items-center ml-4">
+                <div class="inline-block">
+                  {#if canGoBack}
+                    <CircleIconButton logo={ChevronLeft} backgroundColor="#202123" on:click={toPrevious} />
+                  {/if}
+                </div>
+              </div>
+              <div class="flex h-full flex-col place-content-center place-items-center mr-4">
+                <div class="inline-block">
+                  {#if canGoForward}
+                    <CircleIconButton logo={ChevronRight} backgroundColor="#202123" on:click={toNext} />
+                  {/if}
+                </div>
+              </div>
+            </div>
 
-						{#key currentAsset.id}
-							<img
-								transition:fade|local
-								class="rounded-2xl w-full h-full object-contain transition-all"
-								src={api.getAssetThumbnailUrl(currentAsset.id, 'JPEG')}
-								alt=""
-								draggable="false"
-							/>
-						{/key}
+            {#key currentAsset.id}
+              <img
+                transition:fade|local
+                class="rounded-2xl w-full h-full object-contain transition-all"
+                src={api.getAssetThumbnailUrl(currentAsset.id, 'JPEG')}
+                alt=""
+                draggable="false"
+              />
+            {/key}
 
-						<div class="absolute top-4 left-8 text-white text-sm font-medium">
-							<p>
-								{DateTime.fromISO(currentMemory.assets[0].fileCreatedAt).toLocaleString(
-									DateTime.DATE_FULL
-								)}
-							</p>
-							<p>
-								{currentAsset.exifInfo?.city || ''}
-								{currentAsset.exifInfo?.country || ''}
-							</p>
-						</div>
-					</div>
-				</div>
+            <div class="absolute top-4 left-8 text-white text-sm font-medium">
+              <p>
+                {DateTime.fromISO(currentMemory.assets[0].fileCreatedAt).toLocaleString(DateTime.DATE_FULL)}
+              </p>
+              <p>
+                {currentAsset.exifInfo?.city || ''}
+                {currentAsset.exifInfo?.country || ''}
+              </p>
+            </div>
+          </div>
+        </div>
 
-				<!-- NEXT MEMORY -->
-				<div
-					class="rounded-xl w-[20vw] h-1/2"
-					class:opacity-25={nextMemory}
-					class:opacity-0={!nextMemory}
-					class:hover:opacity-70={nextMemory}
-				>
-					<button
-						class="rounded-2xl h-full w-full relative"
-						on:click={toNextMemory}
-						disabled={!nextMemory}
-					>
-						<img
-							class="rounded-2xl h-full w-full object-cover"
-							src={nextMemory
-								? api.getAssetThumbnailUrl(nextMemory.assets[0].id, 'JPEG')
-								: noThumbnailUrl}
-							alt=""
-							draggable="false"
-						/>
+        <!-- NEXT MEMORY -->
+        <div
+          class="rounded-xl w-[20vw] h-1/2"
+          class:opacity-25={nextMemory}
+          class:opacity-0={!nextMemory}
+          class:hover:opacity-70={nextMemory}
+        >
+          <button class="rounded-2xl h-full w-full relative" on:click={toNextMemory} disabled={!nextMemory}>
+            <img
+              class="rounded-2xl h-full w-full object-cover"
+              src={nextMemory ? api.getAssetThumbnailUrl(nextMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
+              alt=""
+              draggable="false"
+            />
 
-						{#if nextMemory}
-							<div class="absolute left-4 bottom-4 text-white text-left">
-								<p class="font-semibold text-xs text-gray-200">UP NEXT</p>
-								<p class="text-xl">{nextMemory.title}</p>
-							</div>
-						{/if}
-					</button>
-				</div>
-			</div>
-		</section>
+            {#if nextMemory}
+              <div class="absolute left-4 bottom-4 text-white text-left">
+                <p class="font-semibold text-xs text-gray-200">UP NEXT</p>
+                <p class="text-xl">{nextMemory.title}</p>
+              </div>
+            {/if}
+          </button>
+        </div>
+      </div>
+    </section>
 
-		<!-- GALERY VIEWER -->
+    <!-- GALERY VIEWER -->
 
-		<section class="bg-immich-dark-gray pl-4">
-			<div
-				class="sticky flex place-content-center place-items-center mb-10 mt-4 transition-all"
-				class:opacity-0={galleryInView}
-				class:opacity-100={!galleryInView}
-			>
-				<button on:click={() => memoryGallery.scrollIntoView({ behavior: 'smooth' })}>
-					<CircleIconButton logo={ChevronDown} backgroundColor="white" forceDark />
-				</button>
-			</div>
+    <section class="bg-immich-dark-gray pl-4">
+      <div
+        class="sticky flex place-content-center place-items-center mb-10 mt-4 transition-all"
+        class:opacity-0={galleryInView}
+        class:opacity-100={!galleryInView}
+      >
+        <button on:click={() => memoryGallery.scrollIntoView({ behavior: 'smooth' })}>
+          <CircleIconButton logo={ChevronDown} backgroundColor="white" forceDark />
+        </button>
+      </div>
 
-			<IntersectionObserver
-				once={false}
-				on:intersected={() => (galleryInView = true)}
-				on:hidden={() => (galleryInView = false)}
-				bottom={-200}
-			>
-				<div id="gallery-memory" bind:this={memoryGallery}>
-					<GalleryViewer assets={currentMemory.assets} viewFrom="album-page" />
-				</div>
-			</IntersectionObserver>
-		</section>
-	{/if}
+      <IntersectionObserver
+        once={false}
+        on:intersected={() => (galleryInView = true)}
+        on:hidden={() => (galleryInView = false)}
+        bottom={-200}
+      >
+        <div id="gallery-memory" bind:this={memoryGallery}>
+          <GalleryViewer assets={currentMemory.assets} viewFrom="album-page" />
+        </div>
+      </IntersectionObserver>
+    </section>
+  {/if}
 </section>
 
 <style>
-	.main-view {
-		box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.3), 0 8px 12px 6px rgba(0, 0, 0, 0.15);
-	}
+  .main-view {
+    box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.3), 0 8px 12px 6px rgba(0, 0, 0, 0.15);
+  }
 </style>
diff --git a/web/src/lib/components/photos-page/actions/add-to-album.svelte b/web/src/lib/components/photos-page/actions/add-to-album.svelte
index 628d98f418..90e2ceff45 100644
--- a/web/src/lib/components/photos-page/actions/add-to-album.svelte
+++ b/web/src/lib/components/photos-page/actions/add-to-album.svelte
@@ -1,67 +1,64 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
-	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-	import {
-		NotificationType,
-		notificationController
-	} from '$lib/components/shared-components/notification/notification';
-	import { addAssetsToAlbum } from '$lib/utils/asset-utils';
-	import { AlbumResponseDto, api } from '@api';
-	import { getMenuContext } from '../asset-select-context-menu.svelte';
-	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import { goto } from '$app/navigation';
+  import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
+  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+  import {
+    NotificationType,
+    notificationController,
+  } from '$lib/components/shared-components/notification/notification';
+  import { addAssetsToAlbum } from '$lib/utils/asset-utils';
+  import { AlbumResponseDto, api } from '@api';
+  import { getMenuContext } from '../asset-select-context-menu.svelte';
+  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 
-	export let shared = false;
-	let showAlbumPicker = false;
+  export let shared = false;
+  let showAlbumPicker = false;
 
-	const { getAssets, clearSelect } = getAssetControlContext();
-	const closeMenu = getMenuContext();
+  const { getAssets, clearSelect } = getAssetControlContext();
+  const closeMenu = getMenuContext();
 
-	const handleHideAlbumPicker = () => {
-		showAlbumPicker = false;
-		closeMenu();
-	};
+  const handleHideAlbumPicker = () => {
+    showAlbumPicker = false;
+    closeMenu();
+  };
 
-	const handleAddToNewAlbum = (event: CustomEvent) => {
-		showAlbumPicker = false;
+  const handleAddToNewAlbum = (event: CustomEvent) => {
+    showAlbumPicker = false;
 
-		const { albumName }: { albumName: string } = event.detail;
-		const assetIds = Array.from(getAssets()).map((asset) => asset.id);
-		api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => {
-			const { id, albumName } = response.data;
+    const { albumName }: { albumName: string } = event.detail;
+    const assetIds = Array.from(getAssets()).map((asset) => asset.id);
+    api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => {
+      const { id, albumName } = response.data;
 
-			notificationController.show({
-				message: `Added ${assetIds.length} to ${albumName}`,
-				type: NotificationType.Info
-			});
+      notificationController.show({
+        message: `Added ${assetIds.length} to ${albumName}`,
+        type: NotificationType.Info,
+      });
 
-			clearSelect();
+      clearSelect();
 
-			goto('/albums/' + id);
-		});
-	};
+      goto('/albums/' + id);
+    });
+  };
 
-	const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
-		showAlbumPicker = false;
-		const album = event.detail.album;
+  const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
+    showAlbumPicker = false;
+    const album = event.detail.album;
 
-		const assetIds = Array.from(getAssets()).map((asset) => asset.id);
+    const assetIds = Array.from(getAssets()).map((asset) => asset.id);
 
-		addAssetsToAlbum(album.id, assetIds).then(clearSelect);
-	};
+    addAssetsToAlbum(album.id, assetIds).then(clearSelect);
+  };
 </script>
 
-<MenuOption
-	on:click={() => (showAlbumPicker = true)}
-	text={shared ? 'Add to Shared Album' : 'Add to Album'}
-/>
+<MenuOption on:click={() => (showAlbumPicker = true)} text={shared ? 'Add to Shared Album' : 'Add to Album'} />
 
 {#if showAlbumPicker}
-	<AlbumSelectionModal
-		{shared}
-		on:newAlbum={handleAddToNewAlbum}
-		on:newSharedAlbum={handleAddToNewAlbum}
-		on:album={handleAddToAlbum}
-		on:close={handleHideAlbumPicker}
-	/>
+  <AlbumSelectionModal
+    {shared}
+    on:newAlbum={handleAddToNewAlbum}
+    on:newSharedAlbum={handleAddToNewAlbum}
+    on:album={handleAddToAlbum}
+    on:close={handleHideAlbumPicker}
+  />
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/archive-action.svelte b/web/src/lib/components/photos-page/actions/archive-action.svelte
index 9f485f6cc2..a575c125a3 100644
--- a/web/src/lib/components/photos-page/actions/archive-action.svelte
+++ b/web/src/lib/components/photos-page/actions/archive-action.svelte
@@ -1,51 +1,51 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import {
-		NotificationType,
-		notificationController
-	} from '$lib/components/shared-components/notification/notification';
-	import { api } from '@api';
-	import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
-	import ArchiveArrowUpOutline from 'svelte-material-icons/ArchiveArrowUpOutline.svelte';
-	import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
-	import { OnAssetArchive, getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import {
+    NotificationType,
+    notificationController,
+  } from '$lib/components/shared-components/notification/notification';
+  import { api } from '@api';
+  import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
+  import ArchiveArrowUpOutline from 'svelte-material-icons/ArchiveArrowUpOutline.svelte';
+  import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
+  import { OnAssetArchive, getAssetControlContext } from '../asset-select-control-bar.svelte';
 
-	export let onAssetArchive: OnAssetArchive = (asset, isArchived) => {
-		asset.isArchived = isArchived;
-	};
+  export let onAssetArchive: OnAssetArchive = (asset, isArchived) => {
+    asset.isArchived = isArchived;
+  };
 
-	export let menuItem = false;
-	export let unarchive = false;
+  export let menuItem = false;
+  export let unarchive = false;
 
-	$: text = unarchive ? 'Unarchive' : 'Archive';
-	$: logo = unarchive ? ArchiveArrowUpOutline : ArchiveArrowDownOutline;
+  $: text = unarchive ? 'Unarchive' : 'Archive';
+  $: logo = unarchive ? ArchiveArrowUpOutline : ArchiveArrowDownOutline;
 
-	const { getAssets, clearSelect } = getAssetControlContext();
+  const { getAssets, clearSelect } = getAssetControlContext();
 
-	const handleArchive = async () => {
-		const isArchived = !unarchive;
-		let cnt = 0;
+  const handleArchive = async () => {
+    const isArchived = !unarchive;
+    let cnt = 0;
 
-		for (const asset of getAssets()) {
-			if (asset.isArchived !== isArchived) {
-				api.assetApi.updateAsset({ id: asset.id, updateAssetDto: { isArchived } });
+    for (const asset of getAssets()) {
+      if (asset.isArchived !== isArchived) {
+        api.assetApi.updateAsset({ id: asset.id, updateAssetDto: { isArchived } });
 
-				onAssetArchive(asset, isArchived);
-				cnt = cnt + 1;
-			}
-		}
+        onAssetArchive(asset, isArchived);
+        cnt = cnt + 1;
+      }
+    }
 
-		notificationController.show({
-			message: `${isArchived ? 'Archived' : 'Unarchived'} ${cnt}`,
-			type: NotificationType.Info
-		});
+    notificationController.show({
+      message: `${isArchived ? 'Archived' : 'Unarchived'} ${cnt}`,
+      type: NotificationType.Info,
+    });
 
-		clearSelect();
-	};
+    clearSelect();
+  };
 </script>
 
 {#if menuItem}
-	<MenuOption {text} on:click={handleArchive} />
+  <MenuOption {text} on:click={handleArchive} />
 {:else}
-	<CircleIconButton title={text} {logo} on:click={handleArchive} />
+  <CircleIconButton title={text} {logo} on:click={handleArchive} />
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/create-shared-link.svelte b/web/src/lib/components/photos-page/actions/create-shared-link.svelte
index 5f436315ad..7fee3668e3 100644
--- a/web/src/lib/components/photos-page/actions/create-shared-link.svelte
+++ b/web/src/lib/components/photos-page/actions/create-shared-link.svelte
@@ -1,23 +1,23 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
-	import { SharedLinkType } from '@api';
-	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
-	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
+  import { SharedLinkType } from '@api';
+  import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
+  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 
-	let showModal = false;
-	const { getAssets, clearSelect } = getAssetControlContext();
+  let showModal = false;
+  const { getAssets, clearSelect } = getAssetControlContext();
 </script>
 
 <CircleIconButton title="Share" logo={ShareVariantOutline} on:click={() => (showModal = true)} />
 
 {#if showModal}
-	<CreateSharedLinkModal
-		sharedAssets={Array.from(getAssets())}
-		shareType={SharedLinkType.Individual}
-		on:close={() => {
-			showModal = false;
-			clearSelect();
-		}}
-	/>
+  <CreateSharedLinkModal
+    sharedAssets={Array.from(getAssets())}
+    shareType={SharedLinkType.Individual}
+    on:close={() => {
+      showModal = false;
+      clearSelect();
+    }}
+  />
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/delete-assets.svelte b/web/src/lib/components/photos-page/actions/delete-assets.svelte
index f22a842315..6283d6b4df 100644
--- a/web/src/lib/components/photos-page/actions/delete-assets.svelte
+++ b/web/src/lib/components/photos-page/actions/delete-assets.svelte
@@ -1,74 +1,70 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import {
-		NotificationType,
-		notificationController
-	} from '$lib/components/shared-components/notification/notification';
-	import { api } from '@api';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import { OnAssetDelete, getAssetControlContext } from '../asset-select-control-bar.svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
-	import { handleError } from '../../../utils/handle-error';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import {
+    NotificationType,
+    notificationController,
+  } from '$lib/components/shared-components/notification/notification';
+  import { api } from '@api';
+  import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+  import { OnAssetDelete, getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import { handleError } from '../../../utils/handle-error';
 
-	export let onAssetDelete: OnAssetDelete;
-	const { getAssets, clearSelect } = getAssetControlContext();
+  export let onAssetDelete: OnAssetDelete;
+  const { getAssets, clearSelect } = getAssetControlContext();
 
-	let isShowConfirmation = false;
+  let isShowConfirmation = false;
 
-	const handleDelete = async () => {
-		try {
-			let count = 0;
+  const handleDelete = async () => {
+    try {
+      let count = 0;
 
-			const { data: deletedAssets } = await api.assetApi.deleteAsset({
-				deleteAssetDto: {
-					ids: Array.from(getAssets()).map((a) => a.id)
-				}
-			});
+      const { data: deletedAssets } = await api.assetApi.deleteAsset({
+        deleteAssetDto: {
+          ids: Array.from(getAssets()).map((a) => a.id),
+        },
+      });
 
-			for (const asset of deletedAssets) {
-				if (asset.status === 'SUCCESS') {
-					onAssetDelete(asset.id);
-					count++;
-				}
-			}
+      for (const asset of deletedAssets) {
+        if (asset.status === 'SUCCESS') {
+          onAssetDelete(asset.id);
+          count++;
+        }
+      }
 
-			notificationController.show({
-				message: `Deleted ${count}`,
-				type: NotificationType.Info
-			});
+      notificationController.show({
+        message: `Deleted ${count}`,
+        type: NotificationType.Info,
+      });
 
-			clearSelect();
-		} catch (e) {
-			handleError(e, 'Error deleting assets');
-		} finally {
-			isShowConfirmation = false;
-		}
-	};
+      clearSelect();
+    } catch (e) {
+      handleError(e, 'Error deleting assets');
+    } finally {
+      isShowConfirmation = false;
+    }
+  };
 </script>
 
-<CircleIconButton
-	title="Delete"
-	logo={DeleteOutline}
-	on:click={() => (isShowConfirmation = true)}
-/>
+<CircleIconButton title="Delete" logo={DeleteOutline} on:click={() => (isShowConfirmation = true)} />
 
 {#if isShowConfirmation}
-	<ConfirmDialogue
-		title="Delete Asset{getAssets().size > 1 ? 's' : ''}"
-		confirmText="Delete"
-		on:confirm={handleDelete}
-		on:cancel={() => (isShowConfirmation = false)}
-	>
-		<svelte:fragment slot="prompt">
-			<p>
-				Are you sure you want to delete
-				{#if getAssets().size > 1}
-					these <b>{getAssets().size}</b> assets? This will also remove them from their album(s).
-				{:else}
-					this asset? This will also remove it from its album(s).
-				{/if}
-			</p>
-			<p><b>You cannot undo this action!</b></p>
-		</svelte:fragment>
-	</ConfirmDialogue>
+  <ConfirmDialogue
+    title="Delete Asset{getAssets().size > 1 ? 's' : ''}"
+    confirmText="Delete"
+    on:confirm={handleDelete}
+    on:cancel={() => (isShowConfirmation = false)}
+  >
+    <svelte:fragment slot="prompt">
+      <p>
+        Are you sure you want to delete
+        {#if getAssets().size > 1}
+          these <b>{getAssets().size}</b> assets? This will also remove them from their album(s).
+        {:else}
+          this asset? This will also remove it from its album(s).
+        {/if}
+      </p>
+      <p><b>You cannot undo this action!</b></p>
+    </svelte:fragment>
+  </ConfirmDialogue>
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/download-action.svelte b/web/src/lib/components/photos-page/actions/download-action.svelte
index a79be2681b..43b3a9ae42 100644
--- a/web/src/lib/components/photos-page/actions/download-action.svelte
+++ b/web/src/lib/components/photos-page/actions/download-action.svelte
@@ -1,35 +1,30 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
-	import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
-	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
+  import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
+  import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
+  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 
-	export let filename = 'immich.zip';
-	export let sharedLinkKey: string | undefined = undefined;
-	export let menuItem = false;
+  export let filename = 'immich.zip';
+  export let sharedLinkKey: string | undefined = undefined;
+  export let menuItem = false;
 
-	const { getAssets, clearSelect } = getAssetControlContext();
+  const { getAssets, clearSelect } = getAssetControlContext();
 
-	const handleDownloadFiles = async () => {
-		const assets = Array.from(getAssets());
-		if (assets.length === 1) {
-			await downloadFile(assets[0], sharedLinkKey);
-			clearSelect();
-			return;
-		}
+  const handleDownloadFiles = async () => {
+    const assets = Array.from(getAssets());
+    if (assets.length === 1) {
+      await downloadFile(assets[0], sharedLinkKey);
+      clearSelect();
+      return;
+    }
 
-		await downloadArchive(
-			filename,
-			{ assetIds: assets.map((asset) => asset.id) },
-			clearSelect,
-			sharedLinkKey
-		);
-	};
+    await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) }, clearSelect, sharedLinkKey);
+  };
 </script>
 
 {#if menuItem}
-	<MenuOption text="Download" on:click={handleDownloadFiles} />
+  <MenuOption text="Download" on:click={handleDownloadFiles} />
 {:else}
-	<CircleIconButton title="Download" logo={CloudDownloadOutline} on:click={handleDownloadFiles} />
+  <CircleIconButton title="Download" logo={CloudDownloadOutline} on:click={handleDownloadFiles} />
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/favorite-action.svelte b/web/src/lib/components/photos-page/actions/favorite-action.svelte
index 7ce532444a..c427d39345 100644
--- a/web/src/lib/components/photos-page/actions/favorite-action.svelte
+++ b/web/src/lib/components/photos-page/actions/favorite-action.svelte
@@ -1,50 +1,50 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-	import {
-		NotificationType,
-		notificationController
-	} from '$lib/components/shared-components/notification/notification';
-	import { api } from '@api';
-	import HeartMinusOutline from 'svelte-material-icons/HeartMinusOutline.svelte';
-	import HeartOutline from 'svelte-material-icons/HeartOutline.svelte';
-	import { OnAssetFavorite, getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+  import {
+    NotificationType,
+    notificationController,
+  } from '$lib/components/shared-components/notification/notification';
+  import { api } from '@api';
+  import HeartMinusOutline from 'svelte-material-icons/HeartMinusOutline.svelte';
+  import HeartOutline from 'svelte-material-icons/HeartOutline.svelte';
+  import { OnAssetFavorite, getAssetControlContext } from '../asset-select-control-bar.svelte';
 
-	export let onAssetFavorite: OnAssetFavorite = (asset, isFavorite) => {
-		asset.isFavorite = isFavorite;
-	};
+  export let onAssetFavorite: OnAssetFavorite = (asset, isFavorite) => {
+    asset.isFavorite = isFavorite;
+  };
 
-	export let menuItem = false;
-	export let removeFavorite: boolean;
+  export let menuItem = false;
+  export let removeFavorite: boolean;
 
-	$: text = removeFavorite ? 'Remove from Favorites' : 'Favorite';
-	$: logo = removeFavorite ? HeartMinusOutline : HeartOutline;
+  $: text = removeFavorite ? 'Remove from Favorites' : 'Favorite';
+  $: logo = removeFavorite ? HeartMinusOutline : HeartOutline;
 
-	const { getAssets, clearSelect } = getAssetControlContext();
+  const { getAssets, clearSelect } = getAssetControlContext();
 
-	const handleFavorite = () => {
-		const isFavorite = !removeFavorite;
+  const handleFavorite = () => {
+    const isFavorite = !removeFavorite;
 
-		let cnt = 0;
-		for (const asset of getAssets()) {
-			if (asset.isFavorite !== isFavorite) {
-				api.assetApi.updateAsset({ id: asset.id, updateAssetDto: { isFavorite } });
-				onAssetFavorite(asset, isFavorite);
-				cnt = cnt + 1;
-			}
-		}
+    let cnt = 0;
+    for (const asset of getAssets()) {
+      if (asset.isFavorite !== isFavorite) {
+        api.assetApi.updateAsset({ id: asset.id, updateAssetDto: { isFavorite } });
+        onAssetFavorite(asset, isFavorite);
+        cnt = cnt + 1;
+      }
+    }
 
-		notificationController.show({
-			message: isFavorite ? `Added ${cnt} to favorites` : `Removed ${cnt} from favorites`,
-			type: NotificationType.Info
-		});
+    notificationController.show({
+      message: isFavorite ? `Added ${cnt} to favorites` : `Removed ${cnt} from favorites`,
+      type: NotificationType.Info,
+    });
 
-		clearSelect();
-	};
+    clearSelect();
+  };
 </script>
 
 {#if menuItem}
-	<MenuOption {text} on:click={handleFavorite} />
+  <MenuOption {text} on:click={handleFavorite} />
 {:else}
-	<CircleIconButton title={text} {logo} on:click={handleFavorite} />
+  <CircleIconButton title={text} {logo} on:click={handleFavorite} />
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/remove-from-album.svelte b/web/src/lib/components/photos-page/actions/remove-from-album.svelte
index 33a3d011b5..1a553e031f 100644
--- a/web/src/lib/components/photos-page/actions/remove-from-album.svelte
+++ b/web/src/lib/components/photos-page/actions/remove-from-album.svelte
@@ -1,66 +1,62 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import {
-		NotificationType,
-		notificationController
-	} from '$lib/components/shared-components/notification/notification';
-	import { AlbumResponseDto, api } from '@api';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import {
+    NotificationType,
+    notificationController,
+  } from '$lib/components/shared-components/notification/notification';
+  import { AlbumResponseDto, api } from '@api';
+  import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 
-	export let album: AlbumResponseDto;
+  export let album: AlbumResponseDto;
 
-	const { getAssets, clearSelect } = getAssetControlContext();
+  const { getAssets, clearSelect } = getAssetControlContext();
 
-	let isShowConfirmation = false;
+  let isShowConfirmation = false;
 
-	const removeFromAlbum = async () => {
-		try {
-			const { data } = await api.albumApi.removeAssetFromAlbum({
-				id: album.id,
-				removeAssetsDto: {
-					assetIds: Array.from(getAssets()).map((a) => a.id)
-				}
-			});
+  const removeFromAlbum = async () => {
+    try {
+      const { data } = await api.albumApi.removeAssetFromAlbum({
+        id: album.id,
+        removeAssetsDto: {
+          assetIds: Array.from(getAssets()).map((a) => a.id),
+        },
+      });
 
-			album = data;
-			clearSelect();
-		} catch (e) {
-			console.error('Error [album-viewer] [removeAssetFromAlbum]', e);
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error removing assets from album, check console for more details'
-			});
-		} finally {
-			isShowConfirmation = false;
-		}
-	};
+      album = data;
+      clearSelect();
+    } catch (e) {
+      console.error('Error [album-viewer] [removeAssetFromAlbum]', e);
+      notificationController.show({
+        type: NotificationType.Error,
+        message: 'Error removing assets from album, check console for more details',
+      });
+    } finally {
+      isShowConfirmation = false;
+    }
+  };
 </script>
 
-<CircleIconButton
-	title="Remove from album"
-	on:click={() => (isShowConfirmation = true)}
-	logo={DeleteOutline}
-/>
+<CircleIconButton title="Remove from album" on:click={() => (isShowConfirmation = true)} logo={DeleteOutline} />
 
 {#if isShowConfirmation}
-	<ConfirmDialogue
-		title="Remove Asset{getAssets().size > 1 ? 's' : ''}"
-		confirmText="Remove"
-		on:confirm={removeFromAlbum}
-		on:cancel={() => (isShowConfirmation = false)}
-	>
-		<svelte:fragment slot="prompt">
-			<p>
-				Are you sure you want to remove
-				{#if getAssets().size > 1}
-					these <b>{getAssets().size}</b> assets
-				{:else}
-					this asset
-				{/if}
-				from the album?
-			</p>
-		</svelte:fragment>
-	</ConfirmDialogue>
+  <ConfirmDialogue
+    title="Remove Asset{getAssets().size > 1 ? 's' : ''}"
+    confirmText="Remove"
+    on:confirm={removeFromAlbum}
+    on:cancel={() => (isShowConfirmation = false)}
+  >
+    <svelte:fragment slot="prompt">
+      <p>
+        Are you sure you want to remove
+        {#if getAssets().size > 1}
+          these <b>{getAssets().size}</b> assets
+        {:else}
+          this asset
+        {/if}
+        from the album?
+      </p>
+    </svelte:fragment>
+  </ConfirmDialogue>
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte b/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte
index 4dc9642393..08768b9845 100644
--- a/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte
+++ b/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte
@@ -1,65 +1,58 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import { SharedLinkResponseDto, api } from '@api';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
-	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
-	import {
-		NotificationType,
-		notificationController
-	} from '../../shared-components/notification/notification';
-	import { handleError } from '../../../utils/handle-error';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import { SharedLinkResponseDto, api } from '@api';
+  import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+  import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
+  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+  import { NotificationType, notificationController } from '../../shared-components/notification/notification';
+  import { handleError } from '../../../utils/handle-error';
 
-	export let sharedLink: SharedLinkResponseDto;
+  export let sharedLink: SharedLinkResponseDto;
 
-	let removing = false;
+  let removing = false;
 
-	const { getAssets, clearSelect } = getAssetControlContext();
+  const { getAssets, clearSelect } = getAssetControlContext();
 
-	const handleRemove = async () => {
-		try {
-			const { data: results } = await api.sharedLinkApi.removeSharedLinkAssets({
-				id: sharedLink.id,
-				assetIdsDto: {
-					assetIds: Array.from(getAssets()).map((asset) => asset.id)
-				},
-				key: sharedLink.key
-			});
+  const handleRemove = async () => {
+    try {
+      const { data: results } = await api.sharedLinkApi.removeSharedLinkAssets({
+        id: sharedLink.id,
+        assetIdsDto: {
+          assetIds: Array.from(getAssets()).map((asset) => asset.id),
+        },
+        key: sharedLink.key,
+      });
 
-			for (const result of results) {
-				if (!result.success) {
-					continue;
-				}
+      for (const result of results) {
+        if (!result.success) {
+          continue;
+        }
 
-				sharedLink.assets = sharedLink.assets.filter((asset) => asset.id !== result.assetId);
-			}
+        sharedLink.assets = sharedLink.assets.filter((asset) => asset.id !== result.assetId);
+      }
 
-			const count = results.filter((item) => item.success).length;
+      const count = results.filter((item) => item.success).length;
 
-			notificationController.show({
-				type: NotificationType.Info,
-				message: `Removed ${count} assets`
-			});
+      notificationController.show({
+        type: NotificationType.Info,
+        message: `Removed ${count} assets`,
+      });
 
-			clearSelect();
-		} catch (error) {
-			handleError(error, 'Unable to remove assets from shared link');
-		}
-	};
+      clearSelect();
+    } catch (error) {
+      handleError(error, 'Unable to remove assets from shared link');
+    }
+  };
 </script>
 
-<CircleIconButton
-	title="Remove from shared link"
-	on:click={() => (removing = true)}
-	logo={DeleteOutline}
-/>
+<CircleIconButton title="Remove from shared link" on:click={() => (removing = true)} logo={DeleteOutline} />
 
 {#if removing}
-	<ConfirmDialogue
-		title="Remove Assets?"
-		prompt="Are you sure you want to remove {getAssets().size} asset(s) from this shared link?"
-		confirmText="Remove"
-		on:confirm={() => handleRemove()}
-		on:cancel={() => (removing = false)}
-	/>
+  <ConfirmDialogue
+    title="Remove Assets?"
+    prompt="Are you sure you want to remove {getAssets().size} asset(s) from this shared link?"
+    confirmText="Remove"
+    on:confirm={() => handleRemove()}
+    on:cancel={() => (removing = false)}
+  />
 {/if}
diff --git a/web/src/lib/components/photos-page/actions/select-all-assets.svelte b/web/src/lib/components/photos-page/actions/select-all-assets.svelte
index 31e6c3ee32..ff95fb9518 100644
--- a/web/src/lib/components/photos-page/actions/select-all-assets.svelte
+++ b/web/src/lib/components/photos-page/actions/select-all-assets.svelte
@@ -1,41 +1,38 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import SelectAll from 'svelte-material-icons/SelectAll.svelte';
-	import TimerSand from 'svelte-material-icons/TimerSand.svelte';
-	import { assetInteractionStore } from '$lib/stores/asset-interaction.store';
-	import { assetGridState, assetStore } from '$lib/stores/assets.store';
-	import { handleError } from '../../../utils/handle-error';
-	import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import SelectAll from 'svelte-material-icons/SelectAll.svelte';
+  import TimerSand from 'svelte-material-icons/TimerSand.svelte';
+  import { assetInteractionStore } from '$lib/stores/asset-interaction.store';
+  import { assetGridState, assetStore } from '$lib/stores/assets.store';
+  import { handleError } from '../../../utils/handle-error';
+  import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
 
-	let selecting = false;
+  let selecting = false;
 
-	const handleSelectAll = async () => {
-		try {
-			selecting = true;
-			let _assetGridState = new AssetGridState();
-			assetGridState.subscribe((state) => {
-				_assetGridState = state;
-			});
+  const handleSelectAll = async () => {
+    try {
+      selecting = true;
+      let _assetGridState = new AssetGridState();
+      assetGridState.subscribe((state) => {
+        _assetGridState = state;
+      });
 
-			for (let i = 0; i < _assetGridState.buckets.length; i++) {
-				await assetStore.getAssetsByBucket(
-					_assetGridState.buckets[i].bucketDate,
-					BucketPosition.Unknown
-				);
-				for (const asset of _assetGridState.buckets[i].assets) {
-					assetInteractionStore.addAssetToMultiselectGroup(asset);
-				}
-			}
-			selecting = false;
-		} catch (e) {
-			handleError(e, 'Error selecting all assets');
-		}
-	};
+      for (let i = 0; i < _assetGridState.buckets.length; i++) {
+        await assetStore.getAssetsByBucket(_assetGridState.buckets[i].bucketDate, BucketPosition.Unknown);
+        for (const asset of _assetGridState.buckets[i].assets) {
+          assetInteractionStore.addAssetToMultiselectGroup(asset);
+        }
+      }
+      selecting = false;
+    } catch (e) {
+      handleError(e, 'Error selecting all assets');
+    }
+  };
 </script>
 
 {#if selecting}
-	<CircleIconButton title="Delete" logo={TimerSand} />
+  <CircleIconButton title="Delete" logo={TimerSand} />
 {/if}
 {#if !selecting}
-	<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
+  <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
 {/if}
diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte
index 5733d73d99..fc9eaf2a6d 100644
--- a/web/src/lib/components/photos-page/asset-date-group.svelte
+++ b/web/src/lib/components/photos-page/asset-date-group.svelte
@@ -1,244 +1,236 @@
 <script lang="ts">
-	import {
-		assetInteractionStore,
-		assetsInAlbumStoreState,
-		isMultiSelectStoreState,
-		selectedAssets,
-		selectedGroup
-	} from '$lib/stores/asset-interaction.store';
-	import { assetStore } from '$lib/stores/assets.store';
-	import { locale } from '$lib/stores/preferences.store';
-	import type { AssetResponseDto } from '@api';
-	import justifiedLayout from 'justified-layout';
-	import lodash from 'lodash-es';
-	import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
-	import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
-	import { fly } from 'svelte/transition';
-	import { getAssetRatio } from '$lib/utils/asset-utils';
-	import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
-	import { createEventDispatcher } from 'svelte';
+  import {
+    assetInteractionStore,
+    assetsInAlbumStoreState,
+    isMultiSelectStoreState,
+    selectedAssets,
+    selectedGroup,
+  } from '$lib/stores/asset-interaction.store';
+  import { assetStore } from '$lib/stores/assets.store';
+  import { locale } from '$lib/stores/preferences.store';
+  import type { AssetResponseDto } from '@api';
+  import justifiedLayout from 'justified-layout';
+  import lodash from 'lodash-es';
+  import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
+  import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
+  import { fly } from 'svelte/transition';
+  import { getAssetRatio } from '$lib/utils/asset-utils';
+  import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
+  import { createEventDispatcher } from 'svelte';
 
-	export let assets: AssetResponseDto[];
-	export let bucketDate: string;
-	export let bucketHeight: number;
-	export let isAlbumSelectionMode = false;
-	export let viewportWidth: number;
+  export let assets: AssetResponseDto[];
+  export let bucketDate: string;
+  export let bucketHeight: number;
+  export let isAlbumSelectionMode = false;
+  export let viewportWidth: number;
 
-	const groupDateFormat: Intl.DateTimeFormatOptions = {
-		weekday: 'short',
-		month: 'short',
-		day: 'numeric',
-		year: 'numeric'
-	};
+  const groupDateFormat: Intl.DateTimeFormatOptions = {
+    weekday: 'short',
+    month: 'short',
+    day: 'numeric',
+    year: 'numeric',
+  };
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	let isMouseOverGroup = false;
-	let actualBucketHeight: number;
-	let hoveredDateGroup = '';
+  let isMouseOverGroup = false;
+  let actualBucketHeight: number;
+  let hoveredDateGroup = '';
 
-	interface LayoutBox {
-		top: number;
-		left: number;
-		width: number;
-	}
+  interface LayoutBox {
+    top: number;
+    left: number;
+    width: number;
+  }
 
-	$: assetsGroupByDate = lodash
-		.chain(assets)
-		.groupBy((a) => new Date(a.fileCreatedAt).toLocaleDateString($locale, groupDateFormat))
-		.sortBy((group) => assets.indexOf(group[0]))
-		.value();
+  $: assetsGroupByDate = lodash
+    .chain(assets)
+    .groupBy((a) => new Date(a.fileCreatedAt).toLocaleDateString($locale, groupDateFormat))
+    .sortBy((group) => assets.indexOf(group[0]))
+    .value();
 
-	$: geometry = (() => {
-		const geometry = [];
-		for (let group of assetsGroupByDate) {
-			const justifiedLayoutResult = justifiedLayout(group.map(getAssetRatio), {
-				boxSpacing: 2,
-				containerWidth: Math.floor(viewportWidth),
-				containerPadding: 0,
-				targetRowHeightTolerance: 0.15,
-				targetRowHeight: 235
-			});
-			geometry.push({
-				...justifiedLayoutResult,
-				containerWidth: calculateWidth(justifiedLayoutResult.boxes)
-			});
-		}
-		return geometry;
-	})();
+  $: geometry = (() => {
+    const geometry = [];
+    for (let group of assetsGroupByDate) {
+      const justifiedLayoutResult = justifiedLayout(group.map(getAssetRatio), {
+        boxSpacing: 2,
+        containerWidth: Math.floor(viewportWidth),
+        containerPadding: 0,
+        targetRowHeightTolerance: 0.15,
+        targetRowHeight: 235,
+      });
+      geometry.push({
+        ...justifiedLayoutResult,
+        containerWidth: calculateWidth(justifiedLayoutResult.boxes),
+      });
+    }
+    return geometry;
+  })();
 
-	$: {
-		if (actualBucketHeight && actualBucketHeight != 0 && actualBucketHeight != bucketHeight) {
-			const heightDelta = assetStore.updateBucketHeight(bucketDate, actualBucketHeight);
-			if (heightDelta !== 0) {
-				scrollTimeline(heightDelta);
-			}
-		}
-	}
+  $: {
+    if (actualBucketHeight && actualBucketHeight != 0 && actualBucketHeight != bucketHeight) {
+      const heightDelta = assetStore.updateBucketHeight(bucketDate, actualBucketHeight);
+      if (heightDelta !== 0) {
+        scrollTimeline(heightDelta);
+      }
+    }
+  }
 
-	function scrollTimeline(heightDelta: number) {
-		dispatch('shift', {
-			heightDelta
-		});
-	}
+  function scrollTimeline(heightDelta: number) {
+    dispatch('shift', {
+      heightDelta,
+    });
+  }
 
-	const calculateWidth = (boxes: LayoutBox[]): number => {
-		let width = 0;
-		for (const box of boxes) {
-			if (box.top < 100) {
-				width = box.left + box.width;
-			}
-		}
+  const calculateWidth = (boxes: LayoutBox[]): number => {
+    let width = 0;
+    for (const box of boxes) {
+      if (box.top < 100) {
+        width = box.left + box.width;
+      }
+    }
 
-		return width;
-	};
+    return width;
+  };
 
-	const assetClickHandler = (
-		asset: AssetResponseDto,
-		assetsInDateGroup: AssetResponseDto[],
-		dateGroupTitle: string
-	) => {
-		if (isAlbumSelectionMode) {
-			assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
-			return;
-		}
+  const assetClickHandler = (
+    asset: AssetResponseDto,
+    assetsInDateGroup: AssetResponseDto[],
+    dateGroupTitle: string,
+  ) => {
+    if (isAlbumSelectionMode) {
+      assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
+      return;
+    }
 
-		if ($isMultiSelectStoreState) {
-			assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
-		} else {
-			assetInteractionStore.setViewingAsset(asset);
-		}
-	};
+    if ($isMultiSelectStoreState) {
+      assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
+    } else {
+      assetInteractionStore.setViewingAsset(asset);
+    }
+  };
 
-	const selectAssetGroupHandler = (
-		selectAssetGroupHandler: AssetResponseDto[],
-		dateGroupTitle: string
-	) => {
-		if ($selectedGroup.has(dateGroupTitle)) {
-			assetInteractionStore.removeGroupFromMultiselectGroup(dateGroupTitle);
-			selectAssetGroupHandler.forEach((asset) => {
-				assetInteractionStore.removeAssetFromMultiselectGroup(asset);
-			});
-		} else {
-			assetInteractionStore.addGroupToMultiselectGroup(dateGroupTitle);
-			selectAssetGroupHandler.forEach((asset) => {
-				assetInteractionStore.addAssetToMultiselectGroup(asset);
-			});
-		}
-	};
+  const selectAssetGroupHandler = (selectAssetGroupHandler: AssetResponseDto[], dateGroupTitle: string) => {
+    if ($selectedGroup.has(dateGroupTitle)) {
+      assetInteractionStore.removeGroupFromMultiselectGroup(dateGroupTitle);
+      selectAssetGroupHandler.forEach((asset) => {
+        assetInteractionStore.removeAssetFromMultiselectGroup(asset);
+      });
+    } else {
+      assetInteractionStore.addGroupToMultiselectGroup(dateGroupTitle);
+      selectAssetGroupHandler.forEach((asset) => {
+        assetInteractionStore.addAssetToMultiselectGroup(asset);
+      });
+    }
+  };
 
-	const assetSelectHandler = (
-		asset: AssetResponseDto,
-		assetsInDateGroup: AssetResponseDto[],
-		dateGroupTitle: string
-	) => {
-		if ($selectedAssets.has(asset)) {
-			assetInteractionStore.removeAssetFromMultiselectGroup(asset);
-		} else {
-			assetInteractionStore.addAssetToMultiselectGroup(asset);
-		}
+  const assetSelectHandler = (
+    asset: AssetResponseDto,
+    assetsInDateGroup: AssetResponseDto[],
+    dateGroupTitle: string,
+  ) => {
+    if ($selectedAssets.has(asset)) {
+      assetInteractionStore.removeAssetFromMultiselectGroup(asset);
+    } else {
+      assetInteractionStore.addAssetToMultiselectGroup(asset);
+    }
 
-		// Check if all assets are selected in a group to toggle the group selection's icon
-		let selectedAssetsInGroupCount = 0;
-		assetsInDateGroup.forEach((asset) => {
-			if ($selectedAssets.has(asset)) {
-				selectedAssetsInGroupCount++;
-			}
-		});
+    // Check if all assets are selected in a group to toggle the group selection's icon
+    let selectedAssetsInGroupCount = 0;
+    assetsInDateGroup.forEach((asset) => {
+      if ($selectedAssets.has(asset)) {
+        selectedAssetsInGroupCount++;
+      }
+    });
 
-		// if all assets are selected in a group, add the group to selected group
-		if (selectedAssetsInGroupCount == assetsInDateGroup.length) {
-			assetInteractionStore.addGroupToMultiselectGroup(dateGroupTitle);
-		} else {
-			assetInteractionStore.removeGroupFromMultiselectGroup(dateGroupTitle);
-		}
-	};
+    // if all assets are selected in a group, add the group to selected group
+    if (selectedAssetsInGroupCount == assetsInDateGroup.length) {
+      assetInteractionStore.addGroupToMultiselectGroup(dateGroupTitle);
+    } else {
+      assetInteractionStore.removeGroupFromMultiselectGroup(dateGroupTitle);
+    }
+  };
 
-	const assetMouseEventHandler = (dateGroupTitle: string) => {
-		// Show multi select icon on hover on date group
-		hoveredDateGroup = dateGroupTitle;
-	};
+  const assetMouseEventHandler = (dateGroupTitle: string) => {
+    // Show multi select icon on hover on date group
+    hoveredDateGroup = dateGroupTitle;
+  };
 </script>
 
 <section
-	id="asset-group-by-date"
-	class="flex flex-wrap gap-x-12"
-	bind:clientHeight={actualBucketHeight}
-	bind:clientWidth={viewportWidth}
+  id="asset-group-by-date"
+  class="flex flex-wrap gap-x-12"
+  bind:clientHeight={actualBucketHeight}
+  bind:clientWidth={viewportWidth}
 >
-	{#each assetsGroupByDate as assetsInDateGroup, groupIndex (assetsInDateGroup[0].id)}
-		{@const dateGroupTitle = new Date(assetsInDateGroup[0].fileCreatedAt).toLocaleDateString(
-			$locale,
-			groupDateFormat
-		)}
-		<!-- Asset Group By Date -->
+  {#each assetsGroupByDate as assetsInDateGroup, groupIndex (assetsInDateGroup[0].id)}
+    {@const dateGroupTitle = new Date(assetsInDateGroup[0].fileCreatedAt).toLocaleDateString($locale, groupDateFormat)}
+    <!-- Asset Group By Date -->
 
-		<div
-			class="flex flex-col mt-5"
-			on:mouseenter={() => {
-				isMouseOverGroup = true;
-				assetMouseEventHandler(dateGroupTitle);
-			}}
-			on:mouseleave={() => (isMouseOverGroup = false)}
-		>
-			<!-- Date group title -->
-			<p
-				class="font-medium text-xs md:text-sm text-immich-fg dark:text-immich-dark-fg mb-2 flex place-items-center h-6"
-				style="width: {geometry[groupIndex].containerWidth}px"
-			>
-				{#if (hoveredDateGroup == dateGroupTitle && isMouseOverGroup) || $selectedGroup.has(dateGroupTitle)}
-					<div
-						transition:fly={{ x: -24, duration: 200, opacity: 0.5 }}
-						class="inline-block px-2 hover:cursor-pointer"
-						on:click={() => selectAssetGroupHandler(assetsInDateGroup, dateGroupTitle)}
-						on:keydown={() => selectAssetGroupHandler(assetsInDateGroup, dateGroupTitle)}
-					>
-						{#if $selectedGroup.has(dateGroupTitle)}
-							<CheckCircle size="24" color="#4250af" />
-						{:else}
-							<CircleOutline size="24" color="#757575" />
-						{/if}
-					</div>
-				{/if}
+    <div
+      class="flex flex-col mt-5"
+      on:mouseenter={() => {
+        isMouseOverGroup = true;
+        assetMouseEventHandler(dateGroupTitle);
+      }}
+      on:mouseleave={() => (isMouseOverGroup = false)}
+    >
+      <!-- Date group title -->
+      <p
+        class="font-medium text-xs md:text-sm text-immich-fg dark:text-immich-dark-fg mb-2 flex place-items-center h-6"
+        style="width: {geometry[groupIndex].containerWidth}px"
+      >
+        {#if (hoveredDateGroup == dateGroupTitle && isMouseOverGroup) || $selectedGroup.has(dateGroupTitle)}
+          <div
+            transition:fly={{ x: -24, duration: 200, opacity: 0.5 }}
+            class="inline-block px-2 hover:cursor-pointer"
+            on:click={() => selectAssetGroupHandler(assetsInDateGroup, dateGroupTitle)}
+            on:keydown={() => selectAssetGroupHandler(assetsInDateGroup, dateGroupTitle)}
+          >
+            {#if $selectedGroup.has(dateGroupTitle)}
+              <CheckCircle size="24" color="#4250af" />
+            {:else}
+              <CircleOutline size="24" color="#757575" />
+            {/if}
+          </div>
+        {/if}
 
-				<span class="truncate" title={dateGroupTitle}>
-					{dateGroupTitle}
-				</span>
-			</p>
+        <span class="truncate" title={dateGroupTitle}>
+          {dateGroupTitle}
+        </span>
+      </p>
 
-			<!-- Image grid -->
-			<div
-				class="relative"
-				style="height: {geometry[groupIndex].containerHeight}px;width: {geometry[groupIndex]
-					.containerWidth}px"
-			>
-				{#each assetsInDateGroup as asset, index (asset.id)}
-					{@const box = geometry[groupIndex].boxes[index]}
-					<div
-						class="absolute"
-						style="width: {box.width}px; height: {box.height}px; top: {box.top}px; left: {box.left}px"
-					>
-						<Thumbnail
-							{asset}
-							{groupIndex}
-							on:click={() => assetClickHandler(asset, assetsInDateGroup, dateGroupTitle)}
-							on:select={() => assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle)}
-							on:mouse-event={() => assetMouseEventHandler(dateGroupTitle)}
-							selected={$selectedAssets.has(asset) ||
-								$assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
-							disabled={$assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
-							thumbnailWidth={box.width}
-							thumbnailHeight={box.height}
-						/>
-					</div>
-				{/each}
-			</div>
-		</div>
-	{/each}
+      <!-- Image grid -->
+      <div
+        class="relative"
+        style="height: {geometry[groupIndex].containerHeight}px;width: {geometry[groupIndex].containerWidth}px"
+      >
+        {#each assetsInDateGroup as asset, index (asset.id)}
+          {@const box = geometry[groupIndex].boxes[index]}
+          <div
+            class="absolute"
+            style="width: {box.width}px; height: {box.height}px; top: {box.top}px; left: {box.left}px"
+          >
+            <Thumbnail
+              {asset}
+              {groupIndex}
+              on:click={() => assetClickHandler(asset, assetsInDateGroup, dateGroupTitle)}
+              on:select={() => assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle)}
+              on:mouse-event={() => assetMouseEventHandler(dateGroupTitle)}
+              selected={$selectedAssets.has(asset) || $assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
+              disabled={$assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
+              thumbnailWidth={box.width}
+              thumbnailHeight={box.height}
+            />
+          </div>
+        {/each}
+      </div>
+    </div>
+  {/each}
 </section>
 
 <style>
-	#asset-group-by-date {
-		contain: layout;
-	}
+  #asset-group-by-date {
+    contain: layout;
+  }
 </style>
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte
index 1a73875776..7e7b7979dc 100644
--- a/web/src/lib/components/photos-page/asset-grid.svelte
+++ b/web/src/lib/components/photos-page/asset-grid.svelte
@@ -1,190 +1,190 @@
 <script lang="ts">
-	import {
-		assetInteractionStore,
-		isViewingAssetStoreState,
-		viewingAssetStoreState
-	} from '$lib/stores/asset-interaction.store';
-	import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
-	import type { UserResponseDto } from '@api';
-	import { AssetCountByTimeBucketResponseDto, AssetResponseDto, TimeGroupEnum, api } from '@api';
-	import { onDestroy, onMount } from 'svelte';
-	import AssetViewer from '../asset-viewer/asset-viewer.svelte';
-	import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
-	import Portal from '../shared-components/portal/portal.svelte';
-	import Scrollbar, {
-		OnScrollbarClickDetail,
-		OnScrollbarDragDetail
-	} from '../shared-components/scrollbar/scrollbar.svelte';
-	import AssetDateGroup from './asset-date-group.svelte';
-	import { BucketPosition } from '$lib/models/asset-grid-state';
-	import MemoryLane from './memory-lane.svelte';
+  import {
+    assetInteractionStore,
+    isViewingAssetStoreState,
+    viewingAssetStoreState,
+  } from '$lib/stores/asset-interaction.store';
+  import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
+  import type { UserResponseDto } from '@api';
+  import { AssetCountByTimeBucketResponseDto, AssetResponseDto, TimeGroupEnum, api } from '@api';
+  import { onDestroy, onMount } from 'svelte';
+  import AssetViewer from '../asset-viewer/asset-viewer.svelte';
+  import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
+  import Portal from '../shared-components/portal/portal.svelte';
+  import Scrollbar, {
+    OnScrollbarClickDetail,
+    OnScrollbarDragDetail,
+  } from '../shared-components/scrollbar/scrollbar.svelte';
+  import AssetDateGroup from './asset-date-group.svelte';
+  import { BucketPosition } from '$lib/models/asset-grid-state';
+  import MemoryLane from './memory-lane.svelte';
 
-	export let user: UserResponseDto | undefined = undefined;
-	export let isAlbumSelectionMode = false;
-	export let showMemoryLane = false;
+  export let user: UserResponseDto | undefined = undefined;
+  export let isAlbumSelectionMode = false;
+  export let showMemoryLane = false;
 
-	let viewportHeight = 0;
-	let viewportWidth = 0;
-	let assetGridElement: HTMLElement;
-	let bucketInfo: AssetCountByTimeBucketResponseDto;
+  let viewportHeight = 0;
+  let viewportWidth = 0;
+  let assetGridElement: HTMLElement;
+  let bucketInfo: AssetCountByTimeBucketResponseDto;
 
-	onMount(async () => {
-		const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({
-			getAssetCountByTimeBucketDto: {
-				timeGroup: TimeGroupEnum.Month,
-				userId: user?.id,
-				withoutThumbs: true
-			}
-		});
+  onMount(async () => {
+    const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({
+      getAssetCountByTimeBucketDto: {
+        timeGroup: TimeGroupEnum.Month,
+        userId: user?.id,
+        withoutThumbs: true,
+      },
+    });
 
-		bucketInfo = assetCountByTimebucket;
+    bucketInfo = assetCountByTimebucket;
 
-		assetStore.setInitialState(viewportHeight, viewportWidth, assetCountByTimebucket, user?.id);
+    assetStore.setInitialState(viewportHeight, viewportWidth, assetCountByTimebucket, user?.id);
 
-		// Get asset bucket if bucket height is smaller than viewport height
-		let bucketsToFetchInitially: string[] = [];
-		let initialBucketsHeight = 0;
-		$assetGridState.buckets.every((bucket) => {
-			if (initialBucketsHeight < viewportHeight) {
-				initialBucketsHeight += bucket.bucketHeight;
-				bucketsToFetchInitially.push(bucket.bucketDate);
-				return true;
-			} else {
-				return false;
-			}
-		});
+    // Get asset bucket if bucket height is smaller than viewport height
+    let bucketsToFetchInitially: string[] = [];
+    let initialBucketsHeight = 0;
+    $assetGridState.buckets.every((bucket) => {
+      if (initialBucketsHeight < viewportHeight) {
+        initialBucketsHeight += bucket.bucketHeight;
+        bucketsToFetchInitially.push(bucket.bucketDate);
+        return true;
+      } else {
+        return false;
+      }
+    });
 
-		bucketsToFetchInitially.forEach((bucketDate) => {
-			assetStore.getAssetsByBucket(bucketDate, BucketPosition.Visible);
-		});
-	});
+    bucketsToFetchInitially.forEach((bucketDate) => {
+      assetStore.getAssetsByBucket(bucketDate, BucketPosition.Visible);
+    });
+  });
 
-	onDestroy(() => {
-		assetStore.setInitialState(0, 0, { totalCount: 0, buckets: [] }, undefined);
-	});
+  onDestroy(() => {
+    assetStore.setInitialState(0, 0, { totalCount: 0, buckets: [] }, undefined);
+  });
 
-	function intersectedHandler(event: CustomEvent) {
-		const el = event.detail.container as HTMLElement;
-		const target = el.firstChild as HTMLElement;
-		if (target) {
-			const bucketDate = target.id.split('_')[1];
-			assetStore.getAssetsByBucket(bucketDate, event.detail.position);
-		}
-	}
+  function intersectedHandler(event: CustomEvent) {
+    const el = event.detail.container as HTMLElement;
+    const target = el.firstChild as HTMLElement;
+    if (target) {
+      const bucketDate = target.id.split('_')[1];
+      assetStore.getAssetsByBucket(bucketDate, event.detail.position);
+    }
+  }
 
-	function handleScrollTimeline(event: CustomEvent) {
-		assetGridElement.scrollBy(0, event.detail.heightDelta);
-	}
+  function handleScrollTimeline(event: CustomEvent) {
+    assetGridElement.scrollBy(0, event.detail.heightDelta);
+  }
 
-	const navigateToPreviousAsset = () => {
-		assetInteractionStore.navigateAsset('previous');
-	};
+  const navigateToPreviousAsset = () => {
+    assetInteractionStore.navigateAsset('previous');
+  };
 
-	const navigateToNextAsset = () => {
-		assetInteractionStore.navigateAsset('next');
-	};
+  const navigateToNextAsset = () => {
+    assetInteractionStore.navigateAsset('next');
+  };
 
-	let lastScrollPosition = 0;
-	let animationTick = false;
+  let lastScrollPosition = 0;
+  let animationTick = false;
 
-	const handleTimelineScroll = () => {
-		if (!animationTick) {
-			window.requestAnimationFrame(() => {
-				lastScrollPosition = assetGridElement?.scrollTop;
-				animationTick = false;
-			});
+  const handleTimelineScroll = () => {
+    if (!animationTick) {
+      window.requestAnimationFrame(() => {
+        lastScrollPosition = assetGridElement?.scrollTop;
+        animationTick = false;
+      });
 
-			animationTick = true;
-		}
-	};
+      animationTick = true;
+    }
+  };
 
-	const handleScrollbarClick = (e: OnScrollbarClickDetail) => {
-		assetGridElement.scrollTop = e.scrollTo;
-	};
+  const handleScrollbarClick = (e: OnScrollbarClickDetail) => {
+    assetGridElement.scrollTop = e.scrollTo;
+  };
 
-	const handleScrollbarDrag = (e: OnScrollbarDragDetail) => {
-		assetGridElement.scrollTop = e.scrollTo;
-	};
+  const handleScrollbarDrag = (e: OnScrollbarDragDetail) => {
+    assetGridElement.scrollTop = e.scrollTo;
+  };
 
-	const handleArchiveSuccess = (e: CustomEvent) => {
-		const asset = e.detail as AssetResponseDto;
-		navigateToNextAsset();
-		assetStore.removeAsset(asset.id);
-	};
+  const handleArchiveSuccess = (e: CustomEvent) => {
+    const asset = e.detail as AssetResponseDto;
+    navigateToNextAsset();
+    assetStore.removeAsset(asset.id);
+  };
 </script>
 
 {#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
-	<Scrollbar
-		scrollbarHeight={viewportHeight}
-		scrollTop={lastScrollPosition}
-		on:onscrollbarclick={(e) => handleScrollbarClick(e.detail)}
-		on:onscrollbardrag={(e) => handleScrollbarDrag(e.detail)}
-	/>
+  <Scrollbar
+    scrollbarHeight={viewportHeight}
+    scrollTop={lastScrollPosition}
+    on:onscrollbarclick={(e) => handleScrollbarClick(e.detail)}
+    on:onscrollbardrag={(e) => handleScrollbarDrag(e.detail)}
+  />
 {/if}
 
 <!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
 <section
-	id="asset-grid"
-	class="overflow-y-auto ml-4 mb-4 mr-[60px] scrollbar-hidden"
-	bind:clientHeight={viewportHeight}
-	bind:clientWidth={viewportWidth}
-	bind:this={assetGridElement}
-	on:scroll={handleTimelineScroll}
+  id="asset-grid"
+  class="overflow-y-auto ml-4 mb-4 mr-[60px] scrollbar-hidden"
+  bind:clientHeight={viewportHeight}
+  bind:clientWidth={viewportWidth}
+  bind:this={assetGridElement}
+  on:scroll={handleTimelineScroll}
 >
-	{#if assetGridElement}
-		{#if showMemoryLane}
-			<MemoryLane />
-		{/if}
-		<section id="virtual-timeline" style:height={$assetGridState.timelineHeight + 'px'}>
-			{#each $assetGridState.buckets as bucket, bucketIndex (bucketIndex)}
-				<IntersectionObserver
-					on:intersected={intersectedHandler}
-					on:hidden={async () => {
-						// If bucket is hidden and in loading state, cancel the request
-						if ($loadingBucketState[bucket.bucketDate]) {
-							await assetStore.cancelBucketRequest(bucket.cancelToken, bucket.bucketDate);
-						}
-					}}
-					let:intersecting
-					top={750}
-					bottom={750}
-					root={assetGridElement}
-				>
-					<div id={'bucket_' + bucket.bucketDate} style:height={bucket.bucketHeight + 'px'}>
-						{#if intersecting}
-							<AssetDateGroup
-								{isAlbumSelectionMode}
-								on:shift={handleScrollTimeline}
-								assets={bucket.assets}
-								bucketDate={bucket.bucketDate}
-								bucketHeight={bucket.bucketHeight}
-								{viewportWidth}
-							/>
-						{/if}
-					</div>
-				</IntersectionObserver>
-			{/each}
-		</section>
-	{/if}
+  {#if assetGridElement}
+    {#if showMemoryLane}
+      <MemoryLane />
+    {/if}
+    <section id="virtual-timeline" style:height={$assetGridState.timelineHeight + 'px'}>
+      {#each $assetGridState.buckets as bucket, bucketIndex (bucketIndex)}
+        <IntersectionObserver
+          on:intersected={intersectedHandler}
+          on:hidden={async () => {
+            // If bucket is hidden and in loading state, cancel the request
+            if ($loadingBucketState[bucket.bucketDate]) {
+              await assetStore.cancelBucketRequest(bucket.cancelToken, bucket.bucketDate);
+            }
+          }}
+          let:intersecting
+          top={750}
+          bottom={750}
+          root={assetGridElement}
+        >
+          <div id={'bucket_' + bucket.bucketDate} style:height={bucket.bucketHeight + 'px'}>
+            {#if intersecting}
+              <AssetDateGroup
+                {isAlbumSelectionMode}
+                on:shift={handleScrollTimeline}
+                assets={bucket.assets}
+                bucketDate={bucket.bucketDate}
+                bucketHeight={bucket.bucketHeight}
+                {viewportWidth}
+              />
+            {/if}
+          </div>
+        </IntersectionObserver>
+      {/each}
+    </section>
+  {/if}
 </section>
 
 <Portal target="body">
-	{#if $isViewingAssetStoreState}
-		<AssetViewer
-			asset={$viewingAssetStoreState}
-			on:navigate-previous={navigateToPreviousAsset}
-			on:navigate-next={navigateToNextAsset}
-			on:close={() => {
-				assetInteractionStore.setIsViewingAsset(false);
-			}}
-			on:archived={handleArchiveSuccess}
-		/>
-	{/if}
+  {#if $isViewingAssetStoreState}
+    <AssetViewer
+      asset={$viewingAssetStoreState}
+      on:navigate-previous={navigateToPreviousAsset}
+      on:navigate-next={navigateToNextAsset}
+      on:close={() => {
+        assetInteractionStore.setIsViewingAsset(false);
+      }}
+      on:archived={handleArchiveSuccess}
+    />
+  {/if}
 </Portal>
 
 <style>
-	#asset-grid {
-		contain: layout;
-		scrollbar-width: none;
-	}
+  #asset-grid {
+    contain: layout;
+    scrollbar-width: none;
+  }
 </style>
diff --git a/web/src/lib/components/photos-page/asset-select-context-menu.svelte b/web/src/lib/components/photos-page/asset-select-context-menu.svelte
index b5f9e6348f..9bfb493cfb 100644
--- a/web/src/lib/components/photos-page/asset-select-context-menu.svelte
+++ b/web/src/lib/components/photos-page/asset-select-context-menu.svelte
@@ -1,35 +1,35 @@
 <script lang="ts" context="module">
-	import { createContext } from '$lib/utils/context';
+  import { createContext } from '$lib/utils/context';
 
-	const { get: getMenuContext, set: setContext } = createContext<() => void>();
-	export { getMenuContext };
+  const { get: getMenuContext, set: setContext } = createContext<() => void>();
+  export { getMenuContext };
 </script>
 
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
-	import type Icon from 'svelte-material-icons/AbTesting.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
+  import type Icon from 'svelte-material-icons/AbTesting.svelte';
 
-	export let icon: typeof Icon;
-	export let title: string;
+  export let icon: typeof Icon;
+  export let title: string;
 
-	let showContextMenu = false;
-	let contextMenuPosition = { x: 0, y: 0 };
+  let showContextMenu = false;
+  let contextMenuPosition = { x: 0, y: 0 };
 
-	const handleShowMenu = ({ x, y }: MouseEvent) => {
-		contextMenuPosition = { x, y };
-		showContextMenu = !showContextMenu;
-	};
+  const handleShowMenu = ({ x, y }: MouseEvent) => {
+    contextMenuPosition = { x, y };
+    showContextMenu = !showContextMenu;
+  };
 
-	setContext(() => (showContextMenu = false));
+  setContext(() => (showContextMenu = false));
 </script>
 
 <CircleIconButton {title} logo={icon} on:click={handleShowMenu} />
 
 {#if showContextMenu}
-	<ContextMenu {...contextMenuPosition} on:outclick={() => (showContextMenu = false)}>
-		<div class="flex flex-col rounded-lg">
-			<slot />
-		</div>
-	</ContextMenu>
+  <ContextMenu {...contextMenuPosition} on:outclick={() => (showContextMenu = false)}>
+    <div class="flex flex-col rounded-lg">
+      <slot />
+    </div>
+  </ContextMenu>
 {/if}
diff --git a/web/src/lib/components/photos-page/asset-select-control-bar.svelte b/web/src/lib/components/photos-page/asset-select-control-bar.svelte
index 3e0f4bcca0..98c5229389 100644
--- a/web/src/lib/components/photos-page/asset-select-control-bar.svelte
+++ b/web/src/lib/components/photos-page/asset-select-control-bar.svelte
@@ -1,39 +1,35 @@
 <script lang="ts" context="module">
-	import { createContext } from '$lib/utils/context';
+  import { createContext } from '$lib/utils/context';
 
-	export type OnAssetDelete = (assetId: string) => void;
-	export type OnAssetArchive = (asset: AssetResponseDto, archived: boolean) => void;
-	export type OnAssetFavorite = (asset: AssetResponseDto, favorite: boolean) => void;
+  export type OnAssetDelete = (assetId: string) => void;
+  export type OnAssetArchive = (asset: AssetResponseDto, archived: boolean) => void;
+  export type OnAssetFavorite = (asset: AssetResponseDto, favorite: boolean) => void;
 
-	export interface AssetControlContext {
-		// Wrap assets in a function, because context isn't reactive.
-		getAssets: () => Set<AssetResponseDto>;
-		clearSelect: () => void;
-	}
+  export interface AssetControlContext {
+    // Wrap assets in a function, because context isn't reactive.
+    getAssets: () => Set<AssetResponseDto>;
+    clearSelect: () => void;
+  }
 
-	const { get: getAssetControlContext, set: setContext } = createContext<AssetControlContext>();
-	export { getAssetControlContext };
+  const { get: getAssetControlContext, set: setContext } = createContext<AssetControlContext>();
+  export { getAssetControlContext };
 </script>
 
 <script lang="ts">
-	import { locale } from '$lib/stores/preferences.store';
-	import type { AssetResponseDto } from '@api';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import ControlAppBar from '../shared-components/control-app-bar.svelte';
+  import { locale } from '$lib/stores/preferences.store';
+  import type { AssetResponseDto } from '@api';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import ControlAppBar from '../shared-components/control-app-bar.svelte';
 
-	export let assets: Set<AssetResponseDto>;
-	export let clearSelect: () => void;
+  export let assets: Set<AssetResponseDto>;
+  export let clearSelect: () => void;
 
-	setContext({ getAssets: () => assets, clearSelect });
+  setContext({ getAssets: () => assets, clearSelect });
 </script>
 
-<ControlAppBar
-	on:close-button-click={clearSelect}
-	backIcon={Close}
-	tailwindClasses="bg-white shadow-md"
->
-	<p class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
-		Selected {assets.size.toLocaleString($locale)}
-	</p>
-	<slot slot="trailing" />
+<ControlAppBar on:close-button-click={clearSelect} backIcon={Close} tailwindClasses="bg-white shadow-md">
+  <p class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
+    Selected {assets.size.toLocaleString($locale)}
+  </p>
+  <slot slot="trailing" />
 </ControlAppBar>
diff --git a/web/src/lib/components/photos-page/memory-lane.svelte b/web/src/lib/components/photos-page/memory-lane.svelte
index 843a38ea98..15d20ff02a 100644
--- a/web/src/lib/components/photos-page/memory-lane.svelte
+++ b/web/src/lib/components/photos-page/memory-lane.svelte
@@ -1,95 +1,95 @@
 <script lang="ts">
-	import { onMount } from 'svelte';
-	import { DateTime } from 'luxon';
-	import { api } from '@api';
-	import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
-	import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
-	import { memoryStore } from '$lib/stores/memory.store';
-	import { goto } from '$app/navigation';
-	import { fade } from 'svelte/transition';
+  import { onMount } from 'svelte';
+  import { DateTime } from 'luxon';
+  import { api } from '@api';
+  import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
+  import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
+  import { memoryStore } from '$lib/stores/memory.store';
+  import { goto } from '$app/navigation';
+  import { fade } from 'svelte/transition';
 
-	$: shouldRender = $memoryStore?.length > 0;
+  $: shouldRender = $memoryStore?.length > 0;
 
-	onMount(async () => {
-		const { data } = await api.assetApi.getMemoryLane({
-			timestamp: DateTime.local().startOf('day').toISO() || ''
-		});
-		$memoryStore = data;
-	});
+  onMount(async () => {
+    const { data } = await api.assetApi.getMemoryLane({
+      timestamp: DateTime.local().startOf('day').toISO() || '',
+    });
+    $memoryStore = data;
+  });
 
-	let memoryLaneElement: HTMLElement;
-	let offsetWidth = 0;
-	let innerWidth = 0;
+  let memoryLaneElement: HTMLElement;
+  let offsetWidth = 0;
+  let innerWidth = 0;
 
-	let scrollLeftPosition = 0;
+  let scrollLeftPosition = 0;
 
-	const onScroll = () => (scrollLeftPosition = memoryLaneElement?.scrollLeft);
+  const onScroll = () => (scrollLeftPosition = memoryLaneElement?.scrollLeft);
 
-	$: canScrollLeft = scrollLeftPosition > 0;
-	$: canScrollRight = Math.ceil(scrollLeftPosition) < innerWidth - offsetWidth;
+  $: canScrollLeft = scrollLeftPosition > 0;
+  $: canScrollRight = Math.ceil(scrollLeftPosition) < innerWidth - offsetWidth;
 
-	const scrollBy = 400;
-	const scrollLeft = () => memoryLaneElement.scrollBy({ left: -scrollBy, behavior: 'smooth' });
-	const scrollRight = () => memoryLaneElement.scrollBy({ left: scrollBy, behavior: 'smooth' });
+  const scrollBy = 400;
+  const scrollLeft = () => memoryLaneElement.scrollBy({ left: -scrollBy, behavior: 'smooth' });
+  const scrollRight = () => memoryLaneElement.scrollBy({ left: scrollBy, behavior: 'smooth' });
 </script>
 
 {#if shouldRender}
-	<section
-		id="memory-lane"
-		bind:this={memoryLaneElement}
-		class="relative overflow-x-hidden whitespace-nowrap mt-5 transition-all"
-		bind:offsetWidth
-		on:scroll={onScroll}
-	>
-		{#if canScrollLeft || canScrollRight}
-			<div class="sticky left-0 z-20">
-				{#if canScrollLeft}
-					<div class="absolute left-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
-						<button
-							class="rounded-full opacity-50 hover:opacity-100 p-2 border border-gray-500 bg-gray-100 text-gray-500"
-							on:click={scrollLeft}
-						>
-							<ChevronLeft size="36" /></button
-						>
-					</div>
-				{/if}
-				{#if canScrollRight}
-					<div class="absolute right-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
-						<button
-							class="rounded-full opacity-50 hover:opacity-100 p-2 border border-gray-500 bg-gray-100 text-gray-500"
-							on:click={scrollRight}
-						>
-							<ChevronRight size="36" /></button
-						>
-					</div>
-				{/if}
-			</div>
-		{/if}
+  <section
+    id="memory-lane"
+    bind:this={memoryLaneElement}
+    class="relative overflow-x-hidden whitespace-nowrap mt-5 transition-all"
+    bind:offsetWidth
+    on:scroll={onScroll}
+  >
+    {#if canScrollLeft || canScrollRight}
+      <div class="sticky left-0 z-20">
+        {#if canScrollLeft}
+          <div class="absolute left-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
+            <button
+              class="rounded-full opacity-50 hover:opacity-100 p-2 border border-gray-500 bg-gray-100 text-gray-500"
+              on:click={scrollLeft}
+            >
+              <ChevronLeft size="36" /></button
+            >
+          </div>
+        {/if}
+        {#if canScrollRight}
+          <div class="absolute right-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
+            <button
+              class="rounded-full opacity-50 hover:opacity-100 p-2 border border-gray-500 bg-gray-100 text-gray-500"
+              on:click={scrollRight}
+            >
+              <ChevronRight size="36" /></button
+            >
+          </div>
+        {/if}
+      </div>
+    {/if}
 
-		<div class="inline-block" bind:offsetWidth={innerWidth}>
-			{#each $memoryStore as memory, i (memory.title)}
-				<button
-					class="memory-card relative inline-block mr-8 rounded-xl aspect-video h-[215px]"
-					on:click={() => goto(`/memory?memory=${i}`)}
-				>
-					<img
-						class="rounded-xl h-full w-full object-cover"
-						src={api.getAssetThumbnailUrl(memory.assets[0].id, 'JPEG')}
-						alt={memory.title}
-						draggable="false"
-					/>
-					<p class="absolute bottom-2 left-4 text-lg text-white z-10">{memory.title}</p>
-					<div
-						class="absolute top-0 left-0 w-full h-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent z-0 hover:bg-black/20 transition-all"
-					/>
-				</button>
-			{/each}
-		</div>
-	</section>
+    <div class="inline-block" bind:offsetWidth={innerWidth}>
+      {#each $memoryStore as memory, i (memory.title)}
+        <button
+          class="memory-card relative inline-block mr-8 rounded-xl aspect-video h-[215px]"
+          on:click={() => goto(`/memory?memory=${i}`)}
+        >
+          <img
+            class="rounded-xl h-full w-full object-cover"
+            src={api.getAssetThumbnailUrl(memory.assets[0].id, 'JPEG')}
+            alt={memory.title}
+            draggable="false"
+          />
+          <p class="absolute bottom-2 left-4 text-lg text-white z-10">{memory.title}</p>
+          <div
+            class="absolute top-0 left-0 w-full h-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent z-0 hover:bg-black/20 transition-all"
+          />
+        </button>
+      {/each}
+    </div>
+  </section>
 {/if}
 
 <style>
-	.memory-card {
-		box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
-	}
+  .memory-card {
+    box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
+  }
 </style>
diff --git a/web/src/lib/components/share-page/individual-shared-viewer.svelte b/web/src/lib/components/share-page/individual-shared-viewer.svelte
index 97e123bf62..aa16f1b458 100644
--- a/web/src/lib/components/share-page/individual-shared-viewer.svelte
+++ b/web/src/lib/components/share-page/individual-shared-viewer.svelte
@@ -1,133 +1,116 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
-	import { downloadArchive } from '$lib/utils/asset-utils';
-	import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
-	import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
-	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import DownloadAction from '../photos-page/actions/download-action.svelte';
-	import RemoveFromSharedLink from '../photos-page/actions/remove-from-shared-link.svelte';
-	import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
-	import ControlAppBar from '../shared-components/control-app-bar.svelte';
-	import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
-	import SelectAll from 'svelte-material-icons/SelectAll.svelte';
-	import ImmichLogo from '../shared-components/immich-logo.svelte';
+  import { goto } from '$app/navigation';
+  import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
+  import { downloadArchive } from '$lib/utils/asset-utils';
+  import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
+  import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
+  import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+  import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
+  import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import DownloadAction from '../photos-page/actions/download-action.svelte';
+  import RemoveFromSharedLink from '../photos-page/actions/remove-from-shared-link.svelte';
+  import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
+  import ControlAppBar from '../shared-components/control-app-bar.svelte';
+  import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
+  import SelectAll from 'svelte-material-icons/SelectAll.svelte';
+  import ImmichLogo from '../shared-components/immich-logo.svelte';
 
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import { handleError } from '../../utils/handle-error';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import { handleError } from '../../utils/handle-error';
 
-	export let sharedLink: SharedLinkResponseDto;
-	export let isOwned: boolean;
+  export let sharedLink: SharedLinkResponseDto;
+  export let isOwned: boolean;
 
-	let selectedAssets: Set<AssetResponseDto> = new Set();
+  let selectedAssets: Set<AssetResponseDto> = new Set();
 
-	$: assets = sharedLink.assets;
-	$: isMultiSelectionMode = selectedAssets.size > 0;
+  $: assets = sharedLink.assets;
+  $: isMultiSelectionMode = selectedAssets.size > 0;
 
-	dragAndDropFilesStore.subscribe((value) => {
-		if (value.isDragging && value.files.length > 0) {
-			handleUploadAssets(value.files);
-			dragAndDropFilesStore.set({ isDragging: false, files: [] });
-		}
-	});
+  dragAndDropFilesStore.subscribe((value) => {
+    if (value.isDragging && value.files.length > 0) {
+      handleUploadAssets(value.files);
+      dragAndDropFilesStore.set({ isDragging: false, files: [] });
+    }
+  });
 
-	const downloadAssets = async () => {
-		await downloadArchive(
-			`immich-shared.zip`,
-			{ assetIds: assets.map((asset) => asset.id) },
-			undefined,
-			sharedLink.key
-		);
-	};
+  const downloadAssets = async () => {
+    await downloadArchive(
+      `immich-shared.zip`,
+      { assetIds: assets.map((asset) => asset.id) },
+      undefined,
+      sharedLink.key,
+    );
+  };
 
-	const handleUploadAssets = async (files: File[] = []) => {
-		try {
-			let results: (string | undefined)[] = [];
-			if (!files || files.length === 0 || !Array.isArray(files)) {
-				results = await openFileUploadDialog(undefined, sharedLink.key);
-			} else {
-				results = await fileUploadHandler(files, undefined, sharedLink.key);
-			}
-			const { data } = await api.sharedLinkApi.addSharedLinkAssets({
-				id: sharedLink.id,
-				assetIdsDto: {
-					assetIds: results.filter((id) => !!id) as string[]
-				},
-				key: sharedLink.key
-			});
+  const handleUploadAssets = async (files: File[] = []) => {
+    try {
+      let results: (string | undefined)[] = [];
+      if (!files || files.length === 0 || !Array.isArray(files)) {
+        results = await openFileUploadDialog(undefined, sharedLink.key);
+      } else {
+        results = await fileUploadHandler(files, undefined, sharedLink.key);
+      }
+      const { data } = await api.sharedLinkApi.addSharedLinkAssets({
+        id: sharedLink.id,
+        assetIdsDto: {
+          assetIds: results.filter((id) => !!id) as string[],
+        },
+        key: sharedLink.key,
+      });
 
-			const added = data.filter((item) => item.success).length;
+      const added = data.filter((item) => item.success).length;
 
-			notificationController.show({
-				message: `Added ${added} assets`,
-				type: NotificationType.Info
-			});
-		} catch (e) {
-			handleError(e, 'Unable to add assets to shared link');
-		}
-	};
+      notificationController.show({
+        message: `Added ${added} assets`,
+        type: NotificationType.Info,
+      });
+    } catch (e) {
+      handleError(e, 'Unable to add assets to shared link');
+    }
+  };
 
-	const handleSelectAll = () => {
-		selectedAssets = new Set(assets);
-	};
+  const handleSelectAll = () => {
+    selectedAssets = new Set(assets);
+  };
 </script>
 
 <section class="bg-immich-bg dark:bg-immich-dark-bg">
-	{#if isMultiSelectionMode}
-		<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
-			<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
-			{#if sharedLink?.allowDownload}
-				<DownloadAction filename="immich-shared.zip" sharedLinkKey={sharedLink.key} />
-			{/if}
-			{#if isOwned}
-				<RemoveFromSharedLink bind:sharedLink />
-			{/if}
-		</AssetSelectControlBar>
-	{:else}
-		<ControlAppBar
-			on:close-button-click={() => goto('/photos')}
-			backIcon={ArrowLeft}
-			showBackButton={false}
-		>
-			<svelte:fragment slot="leading">
-				<a
-					data-sveltekit-preload-data="hover"
-					class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
-					href="https://immich.app"
-				>
-					<ImmichLogo height="30" width="30" />
-					<h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">
-						IMMICH
-					</h1>
-				</a>
-			</svelte:fragment>
+  {#if isMultiSelectionMode}
+    <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
+      <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
+      {#if sharedLink?.allowDownload}
+        <DownloadAction filename="immich-shared.zip" sharedLinkKey={sharedLink.key} />
+      {/if}
+      {#if isOwned}
+        <RemoveFromSharedLink bind:sharedLink />
+      {/if}
+    </AssetSelectControlBar>
+  {:else}
+    <ControlAppBar on:close-button-click={() => goto('/photos')} backIcon={ArrowLeft} showBackButton={false}>
+      <svelte:fragment slot="leading">
+        <a
+          data-sveltekit-preload-data="hover"
+          class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
+          href="https://immich.app"
+        >
+          <ImmichLogo height="30" width="30" />
+          <h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">IMMICH</h1>
+        </a>
+      </svelte:fragment>
 
-			<svelte:fragment slot="trailing">
-				{#if sharedLink?.allowUpload}
-					<CircleIconButton
-						title="Add Photos"
-						on:click={() => handleUploadAssets()}
-						logo={FileImagePlusOutline}
-					/>
-				{/if}
+      <svelte:fragment slot="trailing">
+        {#if sharedLink?.allowUpload}
+          <CircleIconButton title="Add Photos" on:click={() => handleUploadAssets()} logo={FileImagePlusOutline} />
+        {/if}
 
-				{#if sharedLink?.allowDownload}
-					<CircleIconButton
-						title="Download"
-						on:click={downloadAssets}
-						logo={FolderDownloadOutline}
-					/>
-				{/if}
-			</svelte:fragment>
-		</ControlAppBar>
-	{/if}
-	<section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
-		<GalleryViewer {assets} {sharedLink} bind:selectedAssets viewFrom="shared-link-page" />
-	</section>
+        {#if sharedLink?.allowDownload}
+          <CircleIconButton title="Download" on:click={downloadAssets} logo={FolderDownloadOutline} />
+        {/if}
+      </svelte:fragment>
+    </ControlAppBar>
+  {/if}
+  <section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
+    <GalleryViewer {assets} {sharedLink} bind:selectedAssets viewFrom="shared-link-page" />
+  </section>
 </section>
diff --git a/web/src/lib/components/shared-components/album-selection-modal.svelte b/web/src/lib/components/shared-components/album-selection-modal.svelte
index f63c22bb11..1019a9e33e 100644
--- a/web/src/lib/components/shared-components/album-selection-modal.svelte
+++ b/web/src/lib/components/shared-components/album-selection-modal.svelte
@@ -1,121 +1,117 @@
 <script lang="ts">
-	import { AlbumResponseDto, api } from '@api';
-	import { createEventDispatcher, onMount } from 'svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import BaseModal from './base-modal.svelte';
-	import AlbumListItem from '../asset-viewer/album-list-item.svelte';
+  import { AlbumResponseDto, api } from '@api';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import BaseModal from './base-modal.svelte';
+  import AlbumListItem from '../asset-viewer/album-list-item.svelte';
 
-	let albums: AlbumResponseDto[] = [];
-	let recentAlbums: AlbumResponseDto[] = [];
-	let filteredAlbums: AlbumResponseDto[] = [];
-	let loading = true;
-	let search = '';
+  let albums: AlbumResponseDto[] = [];
+  let recentAlbums: AlbumResponseDto[] = [];
+  let filteredAlbums: AlbumResponseDto[] = [];
+  let loading = true;
+  let search = '';
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	export let shared: boolean;
+  export let shared: boolean;
 
-	onMount(async () => {
-		const { data } = await api.albumApi.getAllAlbums({ shared: shared || undefined });
-		albums = data;
+  onMount(async () => {
+    const { data } = await api.albumApi.getAllAlbums({ shared: shared || undefined });
+    albums = data;
 
-		recentAlbums = albums
-			.sort((a, b) => (new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1))
-			.slice(0, 3);
+    recentAlbums = albums.sort((a, b) => (new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1)).slice(0, 3);
 
-		loading = false;
-	});
+    loading = false;
+  });
 
-	$: {
-		if (search.length > 0 && albums.length > 0) {
-			filteredAlbums = albums.filter((album) => {
-				return album.albumName.toLowerCase().includes(search.toLowerCase());
-			});
-		} else {
-			filteredAlbums = albums;
-		}
-	}
+  $: {
+    if (search.length > 0 && albums.length > 0) {
+      filteredAlbums = albums.filter((album) => {
+        return album.albumName.toLowerCase().includes(search.toLowerCase());
+      });
+    } else {
+      filteredAlbums = albums;
+    }
+  }
 
-	const handleSelect = (album: AlbumResponseDto) => {
-		dispatch('album', { album });
-	};
+  const handleSelect = (album: AlbumResponseDto) => {
+    dispatch('album', { album });
+  };
 
-	const handleNew = () => {
-		if (shared) {
-			dispatch('newAlbum', { albumName: search.length > 0 ? search : 'Untitled' });
-		} else {
-			dispatch('newSharedAlbum', { albumName: search.length > 0 ? search : 'Untitled' });
-		}
-	};
+  const handleNew = () => {
+    if (shared) {
+      dispatch('newAlbum', { albumName: search.length > 0 ? search : 'Untitled' });
+    } else {
+      dispatch('newSharedAlbum', { albumName: search.length > 0 ? search : 'Untitled' });
+    }
+  };
 </script>
 
 <BaseModal on:close={() => dispatch('close')}>
-	<svelte:fragment slot="title">
-		<span class="flex gap-2 place-items-center">
-			<p class="font-medium">
-				Add to {#if shared}Shared {/if} Album
-			</p>
-		</span>
-	</svelte:fragment>
+  <svelte:fragment slot="title">
+    <span class="flex gap-2 place-items-center">
+      <p class="font-medium">
+        Add to {#if shared}Shared {/if} Album
+      </p>
+    </span>
+  </svelte:fragment>
 
-	<div class="max-h-[400px] flex flex-col mb-2">
-		{#if loading}
-			{#each { length: 3 } as _}
-				<div class="animate-pulse flex gap-4 px-6 py-2">
-					<div class="h-12 w-12 bg-slate-200 rounded-xl" />
-					<div class="flex flex-col items-start justify-center gap-2">
-						<span class="animate-pulse w-36 h-4 bg-slate-200" />
-						<div class="flex animate-pulse gap-1">
-							<span class="w-8 h-3 bg-slate-200" />
-							<span class="w-20 h-3 bg-slate-200" />
-						</div>
-					</div>
-				</div>
-			{/each}
-		{:else}
-			<!-- svelte-ignore a11y-autofocus -->
-			<input
-				class="px-6 py-2 text-2xl border-b-4 bg-immich-bg border-immich-bg focus:border-immich-primary dark:bg-immich-dark-gray dark:border-immich-dark-gray dark:focus:border-immich-dark-primary"
-				placeholder="Search"
-				autofocus
-				bind:value={search}
-			/>
-			<div class="overflow-y-auto immich-scrollbar">
-				<button
-					on:click={handleNew}
-					class="w-full flex gap-4 px-6 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors items-center"
-				>
-					<div class="h-12 w-12 flex justify-center items-center">
-						<Plus size="30" />
-					</div>
-					<p class="">
-						New {#if shared}Shared {/if}Album {#if search.length > 0}<b>{search}</b>{/if}
-					</p>
-				</button>
-				{#if filteredAlbums.length > 0}
-					{#if !shared && search.length === 0}
-						<p class="text-xs px-5 py-3">RECENT</p>
-						{#each recentAlbums as album (album.id)}
-							<AlbumListItem variant="simple" {album} on:album={() => handleSelect(album)} />
-						{/each}
-					{/if}
+  <div class="max-h-[400px] flex flex-col mb-2">
+    {#if loading}
+      {#each { length: 3 } as _}
+        <div class="animate-pulse flex gap-4 px-6 py-2">
+          <div class="h-12 w-12 bg-slate-200 rounded-xl" />
+          <div class="flex flex-col items-start justify-center gap-2">
+            <span class="animate-pulse w-36 h-4 bg-slate-200" />
+            <div class="flex animate-pulse gap-1">
+              <span class="w-8 h-3 bg-slate-200" />
+              <span class="w-20 h-3 bg-slate-200" />
+            </div>
+          </div>
+        </div>
+      {/each}
+    {:else}
+      <!-- svelte-ignore a11y-autofocus -->
+      <input
+        class="px-6 py-2 text-2xl border-b-4 bg-immich-bg border-immich-bg focus:border-immich-primary dark:bg-immich-dark-gray dark:border-immich-dark-gray dark:focus:border-immich-dark-primary"
+        placeholder="Search"
+        autofocus
+        bind:value={search}
+      />
+      <div class="overflow-y-auto immich-scrollbar">
+        <button
+          on:click={handleNew}
+          class="w-full flex gap-4 px-6 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors items-center"
+        >
+          <div class="h-12 w-12 flex justify-center items-center">
+            <Plus size="30" />
+          </div>
+          <p class="">
+            New {#if shared}Shared {/if}Album {#if search.length > 0}<b>{search}</b>{/if}
+          </p>
+        </button>
+        {#if filteredAlbums.length > 0}
+          {#if !shared && search.length === 0}
+            <p class="text-xs px-5 py-3">RECENT</p>
+            {#each recentAlbums as album (album.id)}
+              <AlbumListItem variant="simple" {album} on:album={() => handleSelect(album)} />
+            {/each}
+          {/if}
 
-					{#if !shared}
-						<p class="text-xs px-5 py-3">
-							{#if search.length === 0}ALL {/if}ALBUMS
-						</p>
-					{/if}
-					{#each filteredAlbums as album (album.id)}
-						<AlbumListItem {album} searchQuery={search} on:album={() => handleSelect(album)} />
-					{/each}
-				{:else if albums.length > 0}
-					<p class="text-sm px-5 py-1">
-						It looks like you do not have any albums with this name yet.
-					</p>
-				{:else}
-					<p class="text-sm px-5 py-1">It looks like you do not have any albums yet.</p>
-				{/if}
-			</div>
-		{/if}
-	</div>
+          {#if !shared}
+            <p class="text-xs px-5 py-3">
+              {#if search.length === 0}ALL {/if}ALBUMS
+            </p>
+          {/if}
+          {#each filteredAlbums as album (album.id)}
+            <AlbumListItem {album} searchQuery={search} on:album={() => handleSelect(album)} />
+          {/each}
+        {:else if albums.length > 0}
+          <p class="text-sm px-5 py-1">It looks like you do not have any albums with this name yet.</p>
+        {:else}
+          <p class="text-sm px-5 py-1">It looks like you do not have any albums yet.</p>
+        {/if}
+      </div>
+    {/if}
+  </div>
 </BaseModal>
diff --git a/web/src/lib/components/shared-components/apple-header.svelte b/web/src/lib/components/shared-components/apple-header.svelte
index 11079a9892..fb45627ede 100644
--- a/web/src/lib/components/shared-components/apple-header.svelte
+++ b/web/src/lib/components/shared-components/apple-header.svelte
@@ -1,184 +1,184 @@
 <script lang="ts">
-	import appleSplash20482732 from '$lib/assets/apple/apple-splash-2048-2732.png';
-	import appleSplash27322048 from '$lib/assets/apple/apple-splash-2732-2048.png';
-	import appleSplash16682388 from '$lib/assets/apple/apple-splash-1668-2388.png';
-	import appleSplash23881668 from '$lib/assets/apple/apple-splash-2388-1668.png';
-	import appleSplash15362048 from '$lib/assets/apple/apple-splash-1536-2048.png';
-	import appleSplash20481536 from '$lib/assets/apple/apple-splash-2048-1536.png';
-	import appleSplash16682224 from '$lib/assets/apple/apple-splash-1668-2224.png';
-	import appleSplash22241668 from '$lib/assets/apple/apple-splash-2224-1668.png';
-	import appleSplash16202160 from '$lib/assets/apple/apple-splash-1620-2160.png';
-	import appleSplash21601620 from '$lib/assets/apple/apple-splash-2160-1620.png';
-	import appleSplash12902796 from '$lib/assets/apple/apple-splash-1290-2796.png';
-	import appleSplash27961290 from '$lib/assets/apple/apple-splash-2796-1290.png';
-	import appleSplash11792556 from '$lib/assets/apple/apple-splash-1179-2556.png';
-	import appleSplash25561179 from '$lib/assets/apple/apple-splash-2556-1179.png';
-	import appleSplash12842778 from '$lib/assets/apple/apple-splash-1284-2778.png';
-	import appleSplash27781284 from '$lib/assets/apple/apple-splash-2778-1284.png';
-	import appleSplash11702532 from '$lib/assets/apple/apple-splash-1170-2532.png';
-	import appleSplash25321170 from '$lib/assets/apple/apple-splash-2532-1170.png';
-	import appleSplash11252436 from '$lib/assets/apple/apple-splash-1125-2436.png';
-	import appleSplash24361125 from '$lib/assets/apple/apple-splash-2436-1125.png';
-	import appleSplash12422688 from '$lib/assets/apple/apple-splash-1242-2688.png';
-	import appleSplash26881242 from '$lib/assets/apple/apple-splash-2688-1242.png';
-	import appleSplash8281792 from '$lib/assets/apple/apple-splash-828-1792.png';
-	import appleSplash1792828 from '$lib/assets/apple/apple-splash-1792-828.png';
-	import appleSplash12422208 from '$lib/assets/apple/apple-splash-1242-2208.png';
-	import appleSplash22081242 from '$lib/assets/apple/apple-splash-2208-1242.png';
-	import appleSplash7501334 from '$lib/assets/apple/apple-splash-750-1334.png';
-	import appleSplash1334750 from '$lib/assets/apple/apple-splash-1334-750.png';
-	import appleSplash6401136 from '$lib/assets/apple/apple-splash-640-1136.png';
-	import appleSplash1136640 from '$lib/assets/apple/apple-splash-1136-640.png';
+  import appleSplash20482732 from '$lib/assets/apple/apple-splash-2048-2732.png';
+  import appleSplash27322048 from '$lib/assets/apple/apple-splash-2732-2048.png';
+  import appleSplash16682388 from '$lib/assets/apple/apple-splash-1668-2388.png';
+  import appleSplash23881668 from '$lib/assets/apple/apple-splash-2388-1668.png';
+  import appleSplash15362048 from '$lib/assets/apple/apple-splash-1536-2048.png';
+  import appleSplash20481536 from '$lib/assets/apple/apple-splash-2048-1536.png';
+  import appleSplash16682224 from '$lib/assets/apple/apple-splash-1668-2224.png';
+  import appleSplash22241668 from '$lib/assets/apple/apple-splash-2224-1668.png';
+  import appleSplash16202160 from '$lib/assets/apple/apple-splash-1620-2160.png';
+  import appleSplash21601620 from '$lib/assets/apple/apple-splash-2160-1620.png';
+  import appleSplash12902796 from '$lib/assets/apple/apple-splash-1290-2796.png';
+  import appleSplash27961290 from '$lib/assets/apple/apple-splash-2796-1290.png';
+  import appleSplash11792556 from '$lib/assets/apple/apple-splash-1179-2556.png';
+  import appleSplash25561179 from '$lib/assets/apple/apple-splash-2556-1179.png';
+  import appleSplash12842778 from '$lib/assets/apple/apple-splash-1284-2778.png';
+  import appleSplash27781284 from '$lib/assets/apple/apple-splash-2778-1284.png';
+  import appleSplash11702532 from '$lib/assets/apple/apple-splash-1170-2532.png';
+  import appleSplash25321170 from '$lib/assets/apple/apple-splash-2532-1170.png';
+  import appleSplash11252436 from '$lib/assets/apple/apple-splash-1125-2436.png';
+  import appleSplash24361125 from '$lib/assets/apple/apple-splash-2436-1125.png';
+  import appleSplash12422688 from '$lib/assets/apple/apple-splash-1242-2688.png';
+  import appleSplash26881242 from '$lib/assets/apple/apple-splash-2688-1242.png';
+  import appleSplash8281792 from '$lib/assets/apple/apple-splash-828-1792.png';
+  import appleSplash1792828 from '$lib/assets/apple/apple-splash-1792-828.png';
+  import appleSplash12422208 from '$lib/assets/apple/apple-splash-1242-2208.png';
+  import appleSplash22081242 from '$lib/assets/apple/apple-splash-2208-1242.png';
+  import appleSplash7501334 from '$lib/assets/apple/apple-splash-750-1334.png';
+  import appleSplash1334750 from '$lib/assets/apple/apple-splash-1334-750.png';
+  import appleSplash6401136 from '$lib/assets/apple/apple-splash-640-1136.png';
+  import appleSplash1136640 from '$lib/assets/apple/apple-splash-1136-640.png';
 </script>
 
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash20482732}
-	media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash20482732}
+  media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash27322048}
-	media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash27322048}
+  media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash16682388}
-	media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash16682388}
+  media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash23881668}
-	media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash23881668}
+  media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash15362048}
-	media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash15362048}
+  media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash20481536}
-	media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash20481536}
+  media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash16682224}
-	media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash16682224}
+  media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash22241668}
-	media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash22241668}
+  media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash16202160}
-	media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash16202160}
+  media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash21601620}
-	media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash21601620}
+  media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash12902796}
-	media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash12902796}
+  media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash27961290}
-	media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash27961290}
+  media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
 />
 
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash11792556}
-	media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash11792556}
+  media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash25561179}
-	media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash25561179}
+  media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash12842778}
-	media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash12842778}
+  media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash27781284}
-	media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash27781284}
+  media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash11702532}
-	media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash11702532}
+  media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash25321170}
-	media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash25321170}
+  media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash11252436}
-	media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash11252436}
+  media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash24361125}
-	media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash24361125}
+  media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash12422688}
-	media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash12422688}
+  media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash26881242}
-	media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash26881242}
+  media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash8281792}
-	media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash8281792}
+  media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash1792828}
-	media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash1792828}
+  media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash12422208}
-	media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash12422208}
+  media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash22081242}
-	media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash22081242}
+  media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash7501334}
-	media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash7501334}
+  media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash1334750}
-	media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash1334750}
+  media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash6401136}
-	media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+  rel="apple-touch-startup-image"
+  href={appleSplash6401136}
+  media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
 />
 <link
-	rel="apple-touch-startup-image"
-	href={appleSplash1136640}
-	media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
+  rel="apple-touch-startup-image"
+  href={appleSplash1136640}
+  media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
 />
diff --git a/web/src/lib/components/shared-components/base-modal.svelte b/web/src/lib/components/shared-components/base-modal.svelte
index 8393b2be8c..33704b5d39 100644
--- a/web/src/lib/components/shared-components/base-modal.svelte
+++ b/web/src/lib/components/shared-components/base-modal.svelte
@@ -1,55 +1,55 @@
 <script lang="ts">
-	import { fade } from 'svelte/transition';
-	import { quintOut } from 'svelte/easing';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import { createEventDispatcher, onMount, onDestroy } from 'svelte';
-	import { browser } from '$app/environment';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import { clickOutside } from '$lib/utils/click-outside';
+  import { fade } from 'svelte/transition';
+  import { quintOut } from 'svelte/easing';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import { createEventDispatcher, onMount, onDestroy } from 'svelte';
+  import { browser } from '$app/environment';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import { clickOutside } from '$lib/utils/click-outside';
 
-	const dispatch = createEventDispatcher();
-	export let zIndex = 9999;
+  const dispatch = createEventDispatcher();
+  export let zIndex = 9999;
 
-	onMount(() => {
-		if (browser) {
-			const scrollTop = document.documentElement.scrollTop;
-			const scrollLeft = document.documentElement.scrollLeft;
-			window.onscroll = function () {
-				window.scrollTo(scrollLeft, scrollTop);
-			};
-		}
-	});
+  onMount(() => {
+    if (browser) {
+      const scrollTop = document.documentElement.scrollTop;
+      const scrollLeft = document.documentElement.scrollLeft;
+      window.onscroll = function () {
+        window.scrollTo(scrollLeft, scrollTop);
+      };
+    }
+  });
 
-	onDestroy(() => {
-		if (browser) {
-			window.onscroll = null;
-		}
-	});
+  onDestroy(() => {
+    if (browser) {
+      window.onscroll = null;
+    }
+  });
 </script>
 
 <div
-	id="immich-modal"
-	style:z-index={zIndex}
-	transition:fade={{ duration: 100, easing: quintOut }}
-	class="fixed top-0 left-0 w-full h-full bg-black/50 flex place-items-center place-content-center overflow-hidden"
+  id="immich-modal"
+  style:z-index={zIndex}
+  transition:fade={{ duration: 100, easing: quintOut }}
+  class="fixed top-0 left-0 w-full h-full bg-black/50 flex place-items-center place-content-center overflow-hidden"
 >
-	<div
-		use:clickOutside
-		on:outclick={() => dispatch('close')}
-		class="bg-immich-bg dark:bg-immich-dark-gray dark:text-immich-dark-fg w-[450px] min-h-[200px] max-h-[600px] rounded-lg shadow-md"
-	>
-		<div class="flex justify-between place-items-center px-5 py-3">
-			<div>
-				<slot name="title">
-					<p>Modal Title</p>
-				</slot>
-			</div>
+  <div
+    use:clickOutside
+    on:outclick={() => dispatch('close')}
+    class="bg-immich-bg dark:bg-immich-dark-gray dark:text-immich-dark-fg w-[450px] min-h-[200px] max-h-[600px] rounded-lg shadow-md"
+  >
+    <div class="flex justify-between place-items-center px-5 py-3">
+      <div>
+        <slot name="title">
+          <p>Modal Title</p>
+        </slot>
+      </div>
 
-			<CircleIconButton on:click={() => dispatch('close')} logo={Close} size={'20'} />
-		</div>
+      <CircleIconButton on:click={() => dispatch('close')} logo={Close} size={'20'} />
+    </div>
 
-		<div class="">
-			<slot />
-		</div>
-	</div>
+    <div class="">
+      <slot />
+    </div>
+  </div>
 </div>
diff --git a/web/src/lib/components/shared-components/confirm-dialogue.svelte b/web/src/lib/components/shared-components/confirm-dialogue.svelte
index 5f65519208..8437a5ab3b 100644
--- a/web/src/lib/components/shared-components/confirm-dialogue.svelte
+++ b/web/src/lib/components/shared-components/confirm-dialogue.svelte
@@ -1,62 +1,57 @@
 <script lang="ts">
-	import { createEventDispatcher } from 'svelte';
-	import FullScreenModal from './full-screen-modal.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import type { Color } from '$lib/components/elements/buttons/button.svelte';
+  import { createEventDispatcher } from 'svelte';
+  import FullScreenModal from './full-screen-modal.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import type { Color } from '$lib/components/elements/buttons/button.svelte';
 
-	export let title = 'Confirm';
-	export let prompt = 'Are you sure you want to do this?';
-	export let confirmText = 'Confirm';
-	export let confirmColor: Color = 'red';
-	export let cancelText = 'Cancel';
-	export let cancelColor: Color = 'primary';
-	export let hideCancelButton = false;
+  export let title = 'Confirm';
+  export let prompt = 'Are you sure you want to do this?';
+  export let confirmText = 'Confirm';
+  export let confirmColor: Color = 'red';
+  export let cancelText = 'Cancel';
+  export let cancelColor: Color = 'primary';
+  export let hideCancelButton = false;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	let isConfirmButtonDisabled = false;
+  let isConfirmButtonDisabled = false;
 
-	const handleCancel = () => dispatch('cancel');
+  const handleCancel = () => dispatch('cancel');
 
-	const handleConfirm = () => {
-		isConfirmButtonDisabled = true;
-		dispatch('confirm');
-	};
+  const handleConfirm = () => {
+    isConfirmButtonDisabled = true;
+    dispatch('confirm');
+  };
 </script>
 
 <FullScreenModal on:clickOutside={handleCancel}>
-	<div
-		class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
-	>
-		<div
-			class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
-		>
-			<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium pb-2">
-				{title}
-			</h1>
-		</div>
-		<div>
-			<div class="px-4 py-5 text-md text-center">
-				<slot name="prompt">
-					<p>{prompt}</p>
-				</slot>
-			</div>
+  <div
+    class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
+  >
+    <div
+      class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
+    >
+      <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium pb-2">
+        {title}
+      </h1>
+    </div>
+    <div>
+      <div class="px-4 py-5 text-md text-center">
+        <slot name="prompt">
+          <p>{prompt}</p>
+        </slot>
+      </div>
 
-			<div class="flex w-full px-4 gap-4 mt-4">
-				{#if !hideCancelButton}
-					<Button color={cancelColor} fullwidth on:click={handleCancel}>
-						{cancelText}
-					</Button>
-				{/if}
-				<Button
-					color={confirmColor}
-					fullwidth
-					on:click={handleConfirm}
-					disabled={isConfirmButtonDisabled}
-				>
-					{confirmText}
-				</Button>
-			</div>
-		</div>
-	</div>
+      <div class="flex w-full px-4 gap-4 mt-4">
+        {#if !hideCancelButton}
+          <Button color={cancelColor} fullwidth on:click={handleCancel}>
+            {cancelText}
+          </Button>
+        {/if}
+        <Button color={confirmColor} fullwidth on:click={handleConfirm} disabled={isConfirmButtonDisabled}>
+          {confirmText}
+        </Button>
+      </div>
+    </div>
+  </div>
 </FullScreenModal>
diff --git a/web/src/lib/components/shared-components/context-menu/context-menu.svelte b/web/src/lib/components/shared-components/context-menu/context-menu.svelte
index aafd9f5d67..484be868e2 100644
--- a/web/src/lib/components/shared-components/context-menu/context-menu.svelte
+++ b/web/src/lib/components/shared-components/context-menu/context-menu.svelte
@@ -1,33 +1,33 @@
 <script lang="ts">
-	import { clickOutside } from '$lib/utils/click-outside';
-	import { quintOut } from 'svelte/easing';
-	import { slide } from 'svelte/transition';
+  import { clickOutside } from '$lib/utils/click-outside';
+  import { quintOut } from 'svelte/easing';
+  import { slide } from 'svelte/transition';
 
-	export let direction: 'left' | 'right' = 'right';
-	export let x = 0;
-	export let y = 0;
+  export let direction: 'left' | 'right' = 'right';
+  export let x = 0;
+  export let y = 0;
 
-	let menuElement: HTMLDivElement;
-	let left: number;
-	let top: number;
+  let menuElement: HTMLDivElement;
+  let left: number;
+  let top: number;
 
-	$: if (menuElement) {
-		const rect = menuElement.getBoundingClientRect();
-		const directionWidth = direction === 'left' ? rect.width : 0;
+  $: if (menuElement) {
+    const rect = menuElement.getBoundingClientRect();
+    const directionWidth = direction === 'left' ? rect.width : 0;
 
-		left = Math.min(window.innerWidth - rect.width, x - directionWidth);
-		top = Math.min(window.innerHeight - rect.height, y);
-	}
+    left = Math.min(window.innerWidth - rect.width, x - directionWidth);
+    top = Math.min(window.innerHeight - rect.height, y);
+  }
 </script>
 
 <div
-	transition:slide={{ duration: 200, easing: quintOut }}
-	bind:this={menuElement}
-	class="absolute w-[200px] z-[99999] rounded-lg overflow-hidden shadow-lg"
-	style="left: {left}px; top: {top}px;"
-	role="menu"
-	use:clickOutside
-	on:outclick
+  transition:slide={{ duration: 200, easing: quintOut }}
+  bind:this={menuElement}
+  class="absolute w-[200px] z-[99999] rounded-lg overflow-hidden shadow-lg"
+  style="left: {left}px; top: {top}px;"
+  role="menu"
+  use:clickOutside
+  on:outclick
 >
-	<slot />
+  <slot />
 </div>
diff --git a/web/src/lib/components/shared-components/context-menu/menu-option.svelte b/web/src/lib/components/shared-components/context-menu/menu-option.svelte
index d8ab462125..bbf94a08e8 100644
--- a/web/src/lib/components/shared-components/context-menu/menu-option.svelte
+++ b/web/src/lib/components/shared-components/context-menu/menu-option.svelte
@@ -1,15 +1,15 @@
 <script>
-	export let text = '';
+  export let text = '';
 </script>
 
 <button
-	on:click
-	class="bg-slate-100 hover:bg-gray-200 text-immich-fg dark:text-immich-dark-bg p-4 w-full text-left text-sm font-medium focus:outline-none focus:ring-inset focus:ring-2"
-	role="menuitem"
+  on:click
+  class="bg-slate-100 hover:bg-gray-200 text-immich-fg dark:text-immich-dark-bg p-4 w-full text-left text-sm font-medium focus:outline-none focus:ring-inset focus:ring-2"
+  role="menuitem"
 >
-	{#if text}
-		{text}
-	{:else}
-		<slot />
-	{/if}
+  {#if text}
+    {text}
+  {:else}
+    <slot />
+  {/if}
 </button>
diff --git a/web/src/lib/components/shared-components/control-app-bar.svelte b/web/src/lib/components/shared-components/control-app-bar.svelte
index ab105e30de..3e32a16aaf 100644
--- a/web/src/lib/components/shared-components/control-app-bar.svelte
+++ b/web/src/lib/components/shared-components/control-app-bar.svelte
@@ -1,72 +1,72 @@
 <script lang="ts">
-	import { browser } from '$app/environment';
+  import { browser } from '$app/environment';
 
-	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import { fly } from 'svelte/transition';
+  import { createEventDispatcher, onDestroy, onMount } from 'svelte';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import { fly } from 'svelte/transition';
 
-	export let showBackButton = true;
-	export let backIcon = Close;
-	export let tailwindClasses = '';
-	export let forceDark = false;
+  export let showBackButton = true;
+  export let backIcon = Close;
+  export let tailwindClasses = '';
+  export let forceDark = false;
 
-	let appBarBorder = 'bg-immich-bg border border-transparent';
+  let appBarBorder = 'bg-immich-bg border border-transparent';
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	const onScroll = () => {
-		if (window.pageYOffset > 80) {
-			appBarBorder = 'border border-gray-200 bg-gray-50 dark:border-gray-600';
+  const onScroll = () => {
+    if (window.pageYOffset > 80) {
+      appBarBorder = 'border border-gray-200 bg-gray-50 dark:border-gray-600';
 
-			if (forceDark) {
-				appBarBorder = 'border border-gray-600';
-			}
-		} else {
-			appBarBorder = 'bg-immich-bg border border-transparent';
-		}
-	};
+      if (forceDark) {
+        appBarBorder = 'border border-gray-600';
+      }
+    } else {
+      appBarBorder = 'bg-immich-bg border border-transparent';
+    }
+  };
 
-	onMount(() => {
-		if (browser) {
-			document.addEventListener('scroll', onScroll);
-		}
-	});
+  onMount(() => {
+    if (browser) {
+      document.addEventListener('scroll', onScroll);
+    }
+  });
 
-	onDestroy(() => {
-		if (browser) {
-			document.removeEventListener('scroll', onScroll);
-		}
-	});
+  onDestroy(() => {
+    if (browser) {
+      document.removeEventListener('scroll', onScroll);
+    }
+  });
 </script>
 
 <div in:fly={{ y: 10, duration: 200 }} class="fixed top-0 w-full bg-transparent z-[100]">
-	<div
-		id="asset-selection-app-bar"
-		class={`grid grid-cols-3 justify-between ${appBarBorder} rounded-lg p-2 mx-2 mt-2 transition-all place-items-center ${tailwindClasses} dark:bg-immich-dark-gray ${
-			forceDark && 'bg-immich-dark-gray text-white'
-		}`}
-	>
-		<div class="flex place-items-center gap-6 dark:text-immich-dark-fg justify-self-start">
-			{#if showBackButton}
-				<CircleIconButton
-					on:click={() => dispatch('close-button-click')}
-					logo={backIcon}
-					backgroundColor={'transparent'}
-					hoverColor={'#e2e7e9'}
-					size={'24'}
-					forceDark
-				/>
-			{/if}
-			<slot name="leading" />
-		</div>
+  <div
+    id="asset-selection-app-bar"
+    class={`grid grid-cols-3 justify-between ${appBarBorder} rounded-lg p-2 mx-2 mt-2 transition-all place-items-center ${tailwindClasses} dark:bg-immich-dark-gray ${
+      forceDark && 'bg-immich-dark-gray text-white'
+    }`}
+  >
+    <div class="flex place-items-center gap-6 dark:text-immich-dark-fg justify-self-start">
+      {#if showBackButton}
+        <CircleIconButton
+          on:click={() => dispatch('close-button-click')}
+          logo={backIcon}
+          backgroundColor={'transparent'}
+          hoverColor={'#e2e7e9'}
+          size={'24'}
+          forceDark
+        />
+      {/if}
+      <slot name="leading" />
+    </div>
 
-		<div class="w-full">
-			<slot />
-		</div>
+    <div class="w-full">
+      <slot />
+    </div>
 
-		<div class="flex place-items-center gap-1 mr-4 justify-self-end">
-			<slot name="trailing" />
-		</div>
-	</div>
+    <div class="flex place-items-center gap-1 mr-4 justify-self-end">
+      <slot name="trailing" />
+    </div>
+  </div>
 </div>
diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte
index b15dc0e61d..547cd154a9 100644
--- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte
+++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte
@@ -1,259 +1,241 @@
 <script lang="ts">
-	import SettingInputField, {
-		SettingInputFieldType
-	} from '$lib/components/admin-page/settings/setting-input-field.svelte';
-	import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte';
-	import Button from '$lib/components/elements/buttons/button.svelte';
-	import { handleError } from '$lib/utils/handle-error';
-	import {
-		AlbumResponseDto,
-		api,
-		AssetResponseDto,
-		SharedLinkResponseDto,
-		SharedLinkType
-	} from '@api';
-	import { createEventDispatcher, onMount } from 'svelte';
-	import Link from 'svelte-material-icons/Link.svelte';
-	import BaseModal from '../base-modal.svelte';
-	import type { ImmichDropDownOption } from '../dropdown-button.svelte';
-	import DropdownButton from '../dropdown-button.svelte';
-	import { notificationController, NotificationType } from '../notification/notification';
+  import SettingInputField, {
+    SettingInputFieldType,
+  } from '$lib/components/admin-page/settings/setting-input-field.svelte';
+  import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte';
+  import Button from '$lib/components/elements/buttons/button.svelte';
+  import { handleError } from '$lib/utils/handle-error';
+  import { AlbumResponseDto, api, AssetResponseDto, SharedLinkResponseDto, SharedLinkType } from '@api';
+  import { createEventDispatcher, onMount } from 'svelte';
+  import Link from 'svelte-material-icons/Link.svelte';
+  import BaseModal from '../base-modal.svelte';
+  import type { ImmichDropDownOption } from '../dropdown-button.svelte';
+  import DropdownButton from '../dropdown-button.svelte';
+  import { notificationController, NotificationType } from '../notification/notification';
 
-	export let shareType: SharedLinkType;
-	export let sharedAssets: AssetResponseDto[] = [];
-	export let album: AlbumResponseDto | undefined = undefined;
-	export let editingLink: SharedLinkResponseDto | undefined = undefined;
+  export let shareType: SharedLinkType;
+  export let sharedAssets: AssetResponseDto[] = [];
+  export let album: AlbumResponseDto | undefined = undefined;
+  export let editingLink: SharedLinkResponseDto | undefined = undefined;
 
-	let sharedLink: string | null = null;
-	let description = '';
-	let allowDownload = true;
-	let allowUpload = false;
-	let showExif = true;
-	let expirationTime = '';
-	let shouldChangeExpirationTime = false;
-	let canCopyImagesToClipboard = true;
-	const dispatch = createEventDispatcher();
+  let sharedLink: string | null = null;
+  let description = '';
+  let allowDownload = true;
+  let allowUpload = false;
+  let showExif = true;
+  let expirationTime = '';
+  let shouldChangeExpirationTime = false;
+  let canCopyImagesToClipboard = true;
+  const dispatch = createEventDispatcher();
 
-	const expiredDateOption: ImmichDropDownOption = {
-		default: 'Never',
-		options: ['Never', '30 minutes', '1 hour', '6 hours', '1 day', '7 days', '30 days']
-	};
+  const expiredDateOption: ImmichDropDownOption = {
+    default: 'Never',
+    options: ['Never', '30 minutes', '1 hour', '6 hours', '1 day', '7 days', '30 days'],
+  };
 
-	onMount(async () => {
-		if (editingLink) {
-			if (editingLink.description) {
-				description = editingLink.description;
-			}
-			allowUpload = editingLink.allowUpload;
-			allowDownload = editingLink.allowDownload;
-			showExif = editingLink.showExif;
-		}
+  onMount(async () => {
+    if (editingLink) {
+      if (editingLink.description) {
+        description = editingLink.description;
+      }
+      allowUpload = editingLink.allowUpload;
+      allowDownload = editingLink.allowDownload;
+      showExif = editingLink.showExif;
+    }
 
-		const module = await import('copy-image-clipboard');
-		canCopyImagesToClipboard = module.canCopyImagesToClipboard();
-	});
+    const module = await import('copy-image-clipboard');
+    canCopyImagesToClipboard = module.canCopyImagesToClipboard();
+  });
 
-	const handleCreateSharedLink = async () => {
-		const expirationTime = getExpirationTimeInMillisecond();
-		const currentTime = new Date().getTime();
-		const expirationDate = expirationTime
-			? new Date(currentTime + expirationTime).toISOString()
-			: undefined;
+  const handleCreateSharedLink = async () => {
+    const expirationTime = getExpirationTimeInMillisecond();
+    const currentTime = new Date().getTime();
+    const expirationDate = expirationTime ? new Date(currentTime + expirationTime).toISOString() : undefined;
 
-		try {
-			const { data } = await api.sharedLinkApi.createSharedLink({
-				sharedLinkCreateDto: {
-					type: shareType,
-					albumId: album ? album.id : undefined,
-					assetIds: sharedAssets.map((a) => a.id),
-					expiresAt: expirationDate,
-					allowUpload,
-					description,
-					allowDownload,
-					showExif
-				}
-			});
-			sharedLink = `${window.location.origin}/share/${data.key}`;
-		} catch (e) {
-			handleError(e, 'Failed to create shared link');
-		}
-	};
+    try {
+      const { data } = await api.sharedLinkApi.createSharedLink({
+        sharedLinkCreateDto: {
+          type: shareType,
+          albumId: album ? album.id : undefined,
+          assetIds: sharedAssets.map((a) => a.id),
+          expiresAt: expirationDate,
+          allowUpload,
+          description,
+          allowDownload,
+          showExif,
+        },
+      });
+      sharedLink = `${window.location.origin}/share/${data.key}`;
+    } catch (e) {
+      handleError(e, 'Failed to create shared link');
+    }
+  };
 
-	const handleCopy = async () => {
-		if (!sharedLink) {
-			return;
-		}
+  const handleCopy = async () => {
+    if (!sharedLink) {
+      return;
+    }
 
-		try {
-			await navigator.clipboard.writeText(sharedLink);
-			notificationController.show({ message: 'Copied to clipboard!', type: NotificationType.Info });
-		} catch (e) {
-			handleError(
-				e,
-				'Cannot copy to clipboard, make sure you are accessing the page through https'
-			);
-		}
-	};
+    try {
+      await navigator.clipboard.writeText(sharedLink);
+      notificationController.show({ message: 'Copied to clipboard!', type: NotificationType.Info });
+    } catch (e) {
+      handleError(e, 'Cannot copy to clipboard, make sure you are accessing the page through https');
+    }
+  };
 
-	const getExpirationTimeInMillisecond = () => {
-		switch (expirationTime) {
-			case '30 minutes':
-				return 30 * 60 * 1000;
-			case '1 hour':
-				return 60 * 60 * 1000;
-			case '6 hours':
-				return 6 * 60 * 60 * 1000;
-			case '1 day':
-				return 24 * 60 * 60 * 1000;
-			case '7 days':
-				return 7 * 24 * 60 * 60 * 1000;
-			case '30 days':
-				return 30 * 24 * 60 * 60 * 1000;
-			default:
-				return 0;
-		}
-	};
+  const getExpirationTimeInMillisecond = () => {
+    switch (expirationTime) {
+      case '30 minutes':
+        return 30 * 60 * 1000;
+      case '1 hour':
+        return 60 * 60 * 1000;
+      case '6 hours':
+        return 6 * 60 * 60 * 1000;
+      case '1 day':
+        return 24 * 60 * 60 * 1000;
+      case '7 days':
+        return 7 * 24 * 60 * 60 * 1000;
+      case '30 days':
+        return 30 * 24 * 60 * 60 * 1000;
+      default:
+        return 0;
+    }
+  };
 
-	const handleEditLink = async () => {
-		if (!editingLink) {
-			return;
-		}
+  const handleEditLink = async () => {
+    if (!editingLink) {
+      return;
+    }
 
-		try {
-			const expirationTime = getExpirationTimeInMillisecond();
-			const currentTime = new Date().getTime();
-			const expirationDate: string | null = expirationTime
-				? new Date(currentTime + expirationTime).toISOString()
-				: null;
+    try {
+      const expirationTime = getExpirationTimeInMillisecond();
+      const currentTime = new Date().getTime();
+      const expirationDate: string | null = expirationTime
+        ? new Date(currentTime + expirationTime).toISOString()
+        : null;
 
-			await api.sharedLinkApi.updateSharedLink({
-				id: editingLink.id,
-				sharedLinkEditDto: {
-					description,
-					expiresAt: shouldChangeExpirationTime ? expirationDate : undefined,
-					allowUpload: allowUpload,
-					allowDownload: allowDownload,
-					showExif: showExif
-				}
-			});
+      await api.sharedLinkApi.updateSharedLink({
+        id: editingLink.id,
+        sharedLinkEditDto: {
+          description,
+          expiresAt: shouldChangeExpirationTime ? expirationDate : undefined,
+          allowUpload: allowUpload,
+          allowDownload: allowDownload,
+          showExif: showExif,
+        },
+      });
 
-			notificationController.show({
-				type: NotificationType.Info,
-				message: 'Edited'
-			});
+      notificationController.show({
+        type: NotificationType.Info,
+        message: 'Edited',
+      });
 
-			dispatch('close');
-		} catch (e) {
-			handleError(e, 'Failed to edit shared link');
-		}
-	};
+      dispatch('close');
+    } catch (e) {
+      handleError(e, 'Failed to edit shared link');
+    }
+  };
 </script>
 
 <BaseModal on:close={() => dispatch('close')}>
-	<svelte:fragment slot="title">
-		<span class="flex gap-2 place-items-center">
-			<Link size={24} />
-			{#if editingLink}
-				<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Edit link</p>
-			{:else}
-				<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Create link to share</p>
-			{/if}
-		</span>
-	</svelte:fragment>
+  <svelte:fragment slot="title">
+    <span class="flex gap-2 place-items-center">
+      <Link size={24} />
+      {#if editingLink}
+        <p class="font-medium text-immich-fg dark:text-immich-dark-fg">Edit link</p>
+      {:else}
+        <p class="font-medium text-immich-fg dark:text-immich-dark-fg">Create link to share</p>
+      {/if}
+    </span>
+  </svelte:fragment>
 
-	<section class="mx-6 mb-6">
-		{#if shareType == SharedLinkType.Album}
-			{#if !editingLink}
-				<div>Let anyone with the link see photos and people in this album.</div>
-			{:else}
-				<div class="text-sm">
-					Public album | <span class="text-immich-primary dark:text-immich-dark-primary"
-						>{editingLink.album?.albumName}</span
-					>
-				</div>
-			{/if}
-		{/if}
+  <section class="mx-6 mb-6">
+    {#if shareType == SharedLinkType.Album}
+      {#if !editingLink}
+        <div>Let anyone with the link see photos and people in this album.</div>
+      {:else}
+        <div class="text-sm">
+          Public album | <span class="text-immich-primary dark:text-immich-dark-primary"
+            >{editingLink.album?.albumName}</span
+          >
+        </div>
+      {/if}
+    {/if}
 
-		{#if shareType == SharedLinkType.Individual}
-			{#if !editingLink}
-				<div>Let anyone with the link see the selected photo(s)</div>
-			{:else}
-				<div class="text-sm">
-					Individual shared | <span class="text-immich-primary dark:text-immich-dark-primary"
-						>{editingLink.description}</span
-					>
-				</div>
-			{/if}
-		{/if}
+    {#if shareType == SharedLinkType.Individual}
+      {#if !editingLink}
+        <div>Let anyone with the link see the selected photo(s)</div>
+      {:else}
+        <div class="text-sm">
+          Individual shared | <span class="text-immich-primary dark:text-immich-dark-primary"
+            >{editingLink.description}</span
+          >
+        </div>
+      {/if}
+    {/if}
 
-		<div class="mt-4 mb-2">
-			<p class="text-xs">LINK OPTIONS</p>
-		</div>
-		<div class="p-4 bg-gray-100 dark:bg-black/40 rounded-lg">
-			<div class="flex flex-col">
-				<div class="mb-2">
-					<SettingInputField
-						inputType={SettingInputFieldType.TEXT}
-						label="Description"
-						bind:value={description}
-					/>
-				</div>
+    <div class="mt-4 mb-2">
+      <p class="text-xs">LINK OPTIONS</p>
+    </div>
+    <div class="p-4 bg-gray-100 dark:bg-black/40 rounded-lg">
+      <div class="flex flex-col">
+        <div class="mb-2">
+          <SettingInputField inputType={SettingInputFieldType.TEXT} label="Description" bind:value={description} />
+        </div>
 
-				<div class="my-3">
-					<SettingSwitch bind:checked={showExif} title={'Show metadata'} />
-				</div>
+        <div class="my-3">
+          <SettingSwitch bind:checked={showExif} title={'Show metadata'} />
+        </div>
 
-				<div class="my-3">
-					<SettingSwitch bind:checked={allowDownload} title={'Allow public user to download'} />
-				</div>
+        <div class="my-3">
+          <SettingSwitch bind:checked={allowDownload} title={'Allow public user to download'} />
+        </div>
 
-				<div class="my-3">
-					<SettingSwitch bind:checked={allowUpload} title={'Allow public user to upload'} />
-				</div>
+        <div class="my-3">
+          <SettingSwitch bind:checked={allowUpload} title={'Allow public user to upload'} />
+        </div>
 
-				<div class="text-sm">
-					{#if editingLink}
-						<p class="my-2 immich-form-label">
-							<SettingSwitch
-								bind:checked={shouldChangeExpirationTime}
-								title={'Change expiration time'}
-							/>
-						</p>
-					{:else}
-						<p class="my-2 immich-form-label">Expire after</p>
-					{/if}
+        <div class="text-sm">
+          {#if editingLink}
+            <p class="my-2 immich-form-label">
+              <SettingSwitch bind:checked={shouldChangeExpirationTime} title={'Change expiration time'} />
+            </p>
+          {:else}
+            <p class="my-2 immich-form-label">Expire after</p>
+          {/if}
 
-					<DropdownButton
-						options={expiredDateOption}
-						bind:selected={expirationTime}
-						disabled={editingLink && !shouldChangeExpirationTime}
-					/>
-				</div>
-			</div>
-		</div>
-	</section>
+          <DropdownButton
+            options={expiredDateOption}
+            bind:selected={expirationTime}
+            disabled={editingLink && !shouldChangeExpirationTime}
+          />
+        </div>
+      </div>
+    </div>
+  </section>
 
-	<hr />
+  <hr />
 
-	<section class="m-6">
-		{#if !sharedLink}
-			{#if editingLink}
-				<div class="flex justify-end">
-					<Button size="sm" rounded="lg" on:click={handleEditLink}>Confirm</Button>
-				</div>
-			{:else}
-				<div class="flex justify-end">
-					<Button size="sm" rounded="lg" on:click={handleCreateSharedLink}>Create link</Button>
-				</div>
-			{/if}
-		{:else}
-			<div class="flex w-full gap-4">
-				<input class="immich-form-input w-full" bind:value={sharedLink} disabled />
+  <section class="m-6">
+    {#if !sharedLink}
+      {#if editingLink}
+        <div class="flex justify-end">
+          <Button size="sm" rounded="lg" on:click={handleEditLink}>Confirm</Button>
+        </div>
+      {:else}
+        <div class="flex justify-end">
+          <Button size="sm" rounded="lg" on:click={handleCreateSharedLink}>Create link</Button>
+        </div>
+      {/if}
+    {:else}
+      <div class="flex w-full gap-4">
+        <input class="immich-form-input w-full" bind:value={sharedLink} disabled />
 
-				{#if canCopyImagesToClipboard}
-					<Button on:click={() => handleCopy()}>Copy</Button>
-				{/if}
-			</div>
-		{/if}
-	</section>
+        {#if canCopyImagesToClipboard}
+          <Button on:click={() => handleCopy()}>Copy</Button>
+        {/if}
+      </div>
+    {/if}
+  </section>
 </BaseModal>
diff --git a/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte b/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte
index 736fdcab4b..f6a6287456 100644
--- a/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte
+++ b/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte
@@ -1,39 +1,39 @@
 <script lang="ts">
-	import { fade } from 'svelte/transition';
-	import ImmichLogo from './immich-logo.svelte';
-	export let dropHandler: (event: DragEvent) => void;
+  import { fade } from 'svelte/transition';
+  import ImmichLogo from './immich-logo.svelte';
+  export let dropHandler: (event: DragEvent) => void;
 
-	let dragStartTarget: EventTarget | null = null;
+  let dragStartTarget: EventTarget | null = null;
 
-	const handleDragEnter = (e: DragEvent) => {
-		dragStartTarget = e.target;
-	};
+  const handleDragEnter = (e: DragEvent) => {
+    dragStartTarget = e.target;
+  };
 </script>
 
 <svelte:body
-	on:dragenter|stopPropagation|preventDefault={handleDragEnter}
-	on:dragleave|stopPropagation|preventDefault={(e) => {
-		if (dragStartTarget === e.target) {
-			dragStartTarget = null;
-		}
-	}}
-	on:drop|stopPropagation|preventDefault={(e) => {
-		dragStartTarget = null;
-		dropHandler(e);
-	}}
+  on:dragenter|stopPropagation|preventDefault={handleDragEnter}
+  on:dragleave|stopPropagation|preventDefault={(e) => {
+    if (dragStartTarget === e.target) {
+      dragStartTarget = null;
+    }
+  }}
+  on:drop|stopPropagation|preventDefault={(e) => {
+    dragStartTarget = null;
+    dropHandler(e);
+  }}
 />
 
 {#if dragStartTarget}
-	<div
-		class="fixed inset-0 w-full h-full z-[1000] flex flex-col items-center justify-center bg-gray-100/90 dark:bg-immich-dark-bg/90 text-immich-dark-gray dark:text-immich-gray"
-		transition:fade={{ duration: 250 }}
-		on:dragover={(e) => {
-			// Prevent browser from opening the dropped file.
-			e.stopPropagation();
-			e.preventDefault();
-		}}
-	>
-		<ImmichLogo class="animate-bounce w-48 m-16" />
-		<div class="text-2xl">Drop files anywhere to upload</div>
-	</div>
+  <div
+    class="fixed inset-0 w-full h-full z-[1000] flex flex-col items-center justify-center bg-gray-100/90 dark:bg-immich-dark-bg/90 text-immich-dark-gray dark:text-immich-gray"
+    transition:fade={{ duration: 250 }}
+    on:dragover={(e) => {
+      // Prevent browser from opening the dropped file.
+      e.stopPropagation();
+      e.preventDefault();
+    }}
+  >
+    <ImmichLogo class="animate-bounce w-48 m-16" />
+    <div class="text-2xl">Drop files anywhere to upload</div>
+  </div>
 {/if}
diff --git a/web/src/lib/components/shared-components/dropdown-button.svelte b/web/src/lib/components/shared-components/dropdown-button.svelte
index 9ad15fbe26..4cf63f7582 100644
--- a/web/src/lib/components/shared-components/dropdown-button.svelte
+++ b/web/src/lib/components/shared-components/dropdown-button.svelte
@@ -1,76 +1,76 @@
 <script lang="ts" context="module">
-	export type ImmichDropDownOption = {
-		default: string;
-		options: string[];
-	};
+  export type ImmichDropDownOption = {
+    default: string;
+    options: string[];
+  };
 </script>
 
 <script lang="ts">
-	import { onMount } from 'svelte';
+  import { onMount } from 'svelte';
 
-	export let options: ImmichDropDownOption;
-	export let selected: string;
-	export let disabled = false;
+  export let options: ImmichDropDownOption;
+  export let selected: string;
+  export let disabled = false;
 
-	onMount(() => {
-		selected = options.default;
-	});
+  onMount(() => {
+    selected = options.default;
+  });
 
-	export let isOpen = false;
-	const toggle = () => (isOpen = !isOpen);
+  export let isOpen = false;
+  const toggle = () => (isOpen = !isOpen);
 </script>
 
 <div id="immich-dropdown" class="relative">
-	<button
-		{disabled}
-		on:click={toggle}
-		aria-expanded={isOpen}
-		class="bg-gray-200 w-full flex p-2 rounded-lg dark:bg-gray-600 place-items-center justify-between disabled:cursor-not-allowed dark:disabled:bg-gray-300 disabled:bg-gray-600"
-	>
-		<div>
-			{selected}
-		</div>
+  <button
+    {disabled}
+    on:click={toggle}
+    aria-expanded={isOpen}
+    class="bg-gray-200 w-full flex p-2 rounded-lg dark:bg-gray-600 place-items-center justify-between disabled:cursor-not-allowed dark:disabled:bg-gray-300 disabled:bg-gray-600"
+  >
+    <div>
+      {selected}
+    </div>
 
-		<div>
-			<svg
-				style="tran"
-				width="20"
-				height="20"
-				fill="none"
-				stroke-linecap="round"
-				stroke-linejoin="round"
-				stroke-width="2"
-				viewBox="0 0 24 24"
-				stroke="currentColor"
-			>
-				<path d="M19 9l-7 7-7-7" />
-			</svg>
-		</div>
-	</button>
+    <div>
+      <svg
+        style="tran"
+        width="20"
+        height="20"
+        fill="none"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        stroke="currentColor"
+      >
+        <path d="M19 9l-7 7-7-7" />
+      </svg>
+    </div>
+  </button>
 
-	{#if isOpen}
-		<div class="flex flex-col mt-2 absolute w-full">
-			{#each options.options as option}
-				<button
-					on:click={() => {
-						selected = option;
-						isOpen = false;
-					}}
-					class="bg-gray-200 dark:bg-gray-500 dark:hover:bg-gray-700 w-full flex p-2 hover:bg-gray-300 transition-all"
-				>
-					{option}
-				</button>
-			{/each}
-		</div>
-	{/if}
+  {#if isOpen}
+    <div class="flex flex-col mt-2 absolute w-full">
+      {#each options.options as option}
+        <button
+          on:click={() => {
+            selected = option;
+            isOpen = false;
+          }}
+          class="bg-gray-200 dark:bg-gray-500 dark:hover:bg-gray-700 w-full flex p-2 hover:bg-gray-300 transition-all"
+        >
+          {option}
+        </button>
+      {/each}
+    </div>
+  {/if}
 </div>
 
 <style>
-	svg {
-		transition: transform 0.2s ease-in;
-	}
+  svg {
+    transition: transform 0.2s ease-in;
+  }
 
-	[aria-expanded='true'] svg {
-		transform: rotate(0.5turn);
-	}
+  [aria-expanded='true'] svg {
+    transform: rotate(0.5turn);
+  }
 </style>
diff --git a/web/src/lib/components/shared-components/empty-placeholder.svelte b/web/src/lib/components/shared-components/empty-placeholder.svelte
index 55b225ac74..d0655aa20b 100644
--- a/web/src/lib/components/shared-components/empty-placeholder.svelte
+++ b/web/src/lib/components/shared-components/empty-placeholder.svelte
@@ -1,28 +1,27 @@
 <script lang="ts">
-	import empty1Url from '$lib/assets/empty-1.svg';
+  import empty1Url from '$lib/assets/empty-1.svg';
 
-	export let actionHandler: undefined | (() => Promise<void>) = undefined;
-	export let text = '';
-	export let alt = '';
+  export let actionHandler: undefined | (() => Promise<void>) = undefined;
+  export let text = '';
+  export let alt = '';
 
-	let hoverClasses =
-		'hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 hover:cursor-pointer';
+  let hoverClasses = 'hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 hover:cursor-pointer';
 </script>
 
 {#if actionHandler}
-	<div
-		on:click={actionHandler}
-		on:keydown={actionHandler}
-		class="border dark:border-immich-dark-gray {hoverClasses} p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
-	>
-		<img src={empty1Url} {alt} width="500" draggable="false" />
-		<p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">{text}</p>
-	</div>
+  <div
+    on:click={actionHandler}
+    on:keydown={actionHandler}
+    class="border dark:border-immich-dark-gray {hoverClasses} p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
+  >
+    <img src={empty1Url} {alt} width="500" draggable="false" />
+    <p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">{text}</p>
+  </div>
 {:else}
-	<div
-		class="border dark:border-immich-dark-gray p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
-	>
-		<img src={empty1Url} {alt} width="500" draggable="false" />
-		<p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">{text}</p>
-	</div>
+  <div
+    class="border dark:border-immich-dark-gray p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
+  >
+    <img src={empty1Url} {alt} width="500" draggable="false" />
+    <p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">{text}</p>
+  </div>
 {/if}
diff --git a/web/src/lib/components/shared-components/favicon-header.svelte b/web/src/lib/components/shared-components/favicon-header.svelte
index 9555a04b23..c61682c910 100644
--- a/web/src/lib/components/shared-components/favicon-header.svelte
+++ b/web/src/lib/components/shared-components/favicon-header.svelte
@@ -1,8 +1,8 @@
 <script lang="ts">
-	import IconAppleTouch180 from '$lib/assets/favicon/apple-icon-180.png';
-	import Icon16 from '$lib/assets/favicon/favicon-16.png';
-	import Icon32 from '$lib/assets/favicon/favicon-32.png';
-	import Icon96 from '$lib/assets/favicon/favicon-96.png';
+  import IconAppleTouch180 from '$lib/assets/favicon/apple-icon-180.png';
+  import Icon16 from '$lib/assets/favicon/favicon-16.png';
+  import Icon32 from '$lib/assets/favicon/favicon-32.png';
+  import Icon96 from '$lib/assets/favicon/favicon-96.png';
 </script>
 
 <link rel="icon" type="image/png" sizes="16x16" href={Icon16} />
diff --git a/web/src/lib/components/shared-components/full-screen-modal.svelte b/web/src/lib/components/shared-components/full-screen-modal.svelte
index 063db78e71..915583b9fc 100644
--- a/web/src/lib/components/shared-components/full-screen-modal.svelte
+++ b/web/src/lib/components/shared-components/full-screen-modal.svelte
@@ -1,17 +1,17 @@
 <script lang="ts">
-	import { clickOutside } from '../../utils/click-outside';
-	import { createEventDispatcher } from 'svelte';
-	import { fade } from 'svelte/transition';
+  import { clickOutside } from '../../utils/click-outside';
+  import { createEventDispatcher } from 'svelte';
+  import { fade } from 'svelte/transition';
 
-	const dispatch = createEventDispatcher<{ clickOutside: void }>();
+  const dispatch = createEventDispatcher<{ clickOutside: void }>();
 </script>
 
 <section
-	in:fade={{ duration: 100 }}
-	out:fade={{ duration: 100 }}
-	class="fixed left-0 top-0 w-screen h-screen bg-black/40 z-[990] flex place-items-center place-content-center"
+  in:fade={{ duration: 100 }}
+  out:fade={{ duration: 100 }}
+  class="fixed left-0 top-0 w-screen h-screen bg-black/40 z-[990] flex place-items-center place-content-center"
 >
-	<div class="z-[9999]" use:clickOutside on:outclick={() => dispatch('clickOutside')}>
-		<slot />
-	</div>
+  <div class="z-[9999]" use:clickOutside on:outclick={() => dispatch('clickOutside')}>
+    <slot />
+  </div>
 </section>
diff --git a/web/src/lib/components/shared-components/fullscreen-container.svelte b/web/src/lib/components/shared-components/fullscreen-container.svelte
index 4bf39362b0..0fb52fdd98 100644
--- a/web/src/lib/components/shared-components/fullscreen-container.svelte
+++ b/web/src/lib/components/shared-components/fullscreen-container.svelte
@@ -1,29 +1,29 @@
 <script lang="ts">
-	import ImmichLogo from './immich-logo.svelte';
+  import ImmichLogo from './immich-logo.svelte';
 
-	export let title: string;
-	export let showMessage = $$slots.message;
+  export let title: string;
+  export let showMessage = $$slots.message;
 </script>
 
 <section class="min-h-screen w-screen flex place-items-center place-content-center p-4">
-	<div
-		class="flex flex-col gap-4 border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-8 shadow-sm w-full max-w-lg rounded-3xl"
-	>
-		<div class="flex flex-col place-items-center place-content-center gap-4 py-4">
-			<ImmichLogo class="h-24 w-24" />
-			<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
-				{title}
-			</h1>
-		</div>
+  <div
+    class="flex flex-col gap-4 border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-8 shadow-sm w-full max-w-lg rounded-3xl"
+  >
+    <div class="flex flex-col place-items-center place-content-center gap-4 py-4">
+      <ImmichLogo class="h-24 w-24" />
+      <h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
+        {title}
+      </h1>
+    </div>
 
-		{#if showMessage}
-			<div
-				class="text-sm rounded-xl p-4 text-immich-primary dark:text-immich-dark-primary font-medium bg-immich-primary/5 dark:border-immich-dark-bg w-full border-immich-primary border-2"
-			>
-				<slot name="message" />
-			</div>
-		{/if}
+    {#if showMessage}
+      <div
+        class="text-sm rounded-xl p-4 text-immich-primary dark:text-immich-dark-primary font-medium bg-immich-primary/5 dark:border-immich-dark-bg w-full border-immich-primary border-2"
+      >
+        <slot name="message" />
+      </div>
+    {/if}
 
-		<slot />
-	</div>
+    <slot />
+  </div>
 </section>
diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte
index ac99c105ef..2f2737d151 100644
--- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte
+++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte
@@ -1,147 +1,142 @@
 <script lang="ts" context="module">
-	export type ViewFrom =
-		| 'archive-page'
-		| 'album-page'
-		| 'favorites-page'
-		| 'search-page'
-		| 'shared-link-page';
+  export type ViewFrom = 'archive-page' | 'album-page' | 'favorites-page' | 'search-page' | 'shared-link-page';
 </script>
 
 <script lang="ts">
-	import { page } from '$app/stores';
-	import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
-	import { handleError } from '$lib/utils/handle-error';
-	import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api';
-	import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
-	import { flip } from 'svelte/animate';
-	import { archivedAsset } from '$lib/stores/archived-asset.store';
+  import { page } from '$app/stores';
+  import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
+  import { handleError } from '$lib/utils/handle-error';
+  import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api';
+  import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
+  import { flip } from 'svelte/animate';
+  import { archivedAsset } from '$lib/stores/archived-asset.store';
 
-	export let assets: AssetResponseDto[];
-	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
-	export let selectedAssets: Set<AssetResponseDto> = new Set();
-	export let disableAssetSelect = false;
-	export let viewFrom: ViewFrom;
-	export let showArchiveIcon = false;
+  export let assets: AssetResponseDto[];
+  export let sharedLink: SharedLinkResponseDto | undefined = undefined;
+  export let selectedAssets: Set<AssetResponseDto> = new Set();
+  export let disableAssetSelect = false;
+  export let viewFrom: ViewFrom;
+  export let showArchiveIcon = false;
 
-	let isShowAssetViewer = false;
+  let isShowAssetViewer = false;
 
-	let selectedAsset: AssetResponseDto;
-	let currentViewAssetIndex = 0;
+  let selectedAsset: AssetResponseDto;
+  let currentViewAssetIndex = 0;
 
-	let viewWidth: number;
-	let thumbnailSize = 300;
+  let viewWidth: number;
+  let thumbnailSize = 300;
 
-	$: isMultiSelectionMode = selectedAssets.size > 0;
+  $: isMultiSelectionMode = selectedAssets.size > 0;
 
-	$: {
-		if (assets.length < 6) {
-			thumbnailSize = Math.min(320, Math.floor(viewWidth / assets.length - assets.length));
-		} else {
-			if (viewWidth > 600) thumbnailSize = Math.floor(viewWidth / 7 - 7);
-			else if (viewWidth > 400) thumbnailSize = Math.floor(viewWidth / 4 - 6);
-			else if (viewWidth > 300) thumbnailSize = Math.floor(viewWidth / 2 - 6);
-			else if (viewWidth > 200) thumbnailSize = Math.floor(viewWidth / 2 - 6);
-			else if (viewWidth > 100) thumbnailSize = Math.floor(viewWidth / 1 - 6);
-		}
-	}
+  $: {
+    if (assets.length < 6) {
+      thumbnailSize = Math.min(320, Math.floor(viewWidth / assets.length - assets.length));
+    } else {
+      if (viewWidth > 600) thumbnailSize = Math.floor(viewWidth / 7 - 7);
+      else if (viewWidth > 400) thumbnailSize = Math.floor(viewWidth / 4 - 6);
+      else if (viewWidth > 300) thumbnailSize = Math.floor(viewWidth / 2 - 6);
+      else if (viewWidth > 200) thumbnailSize = Math.floor(viewWidth / 2 - 6);
+      else if (viewWidth > 100) thumbnailSize = Math.floor(viewWidth / 1 - 6);
+    }
+  }
 
-	const viewAssetHandler = (event: CustomEvent) => {
-		const { asset }: { asset: AssetResponseDto } = event.detail;
+  const viewAssetHandler = (event: CustomEvent) => {
+    const { asset }: { asset: AssetResponseDto } = event.detail;
 
-		currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id);
-		selectedAsset = assets[currentViewAssetIndex];
-		isShowAssetViewer = true;
-		pushState(selectedAsset.id);
-	};
+    currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id);
+    selectedAsset = assets[currentViewAssetIndex];
+    isShowAssetViewer = true;
+    pushState(selectedAsset.id);
+  };
 
-	const selectAssetHandler = (event: CustomEvent) => {
-		const { asset }: { asset: AssetResponseDto } = event.detail;
-		let temp = new Set(selectedAssets);
+  const selectAssetHandler = (event: CustomEvent) => {
+    const { asset }: { asset: AssetResponseDto } = event.detail;
+    let temp = new Set(selectedAssets);
 
-		if (selectedAssets.has(asset)) {
-			temp.delete(asset);
-		} else {
-			temp.add(asset);
-		}
+    if (selectedAssets.has(asset)) {
+      temp.delete(asset);
+    } else {
+      temp.add(asset);
+    }
 
-		selectedAssets = temp;
-	};
+    selectedAssets = temp;
+  };
 
-	const navigateAssetForward = () => {
-		try {
-			if (currentViewAssetIndex < assets.length - 1) {
-				currentViewAssetIndex++;
-				selectedAsset = assets[currentViewAssetIndex];
-				pushState(selectedAsset.id);
-			}
-		} catch (e) {
-			handleError(e, 'Cannot navigate to the next asset');
-		}
-	};
+  const navigateAssetForward = () => {
+    try {
+      if (currentViewAssetIndex < assets.length - 1) {
+        currentViewAssetIndex++;
+        selectedAsset = assets[currentViewAssetIndex];
+        pushState(selectedAsset.id);
+      }
+    } catch (e) {
+      handleError(e, 'Cannot navigate to the next asset');
+    }
+  };
 
-	const navigateAssetBackward = () => {
-		try {
-			if (currentViewAssetIndex > 0) {
-				currentViewAssetIndex--;
-				selectedAsset = assets[currentViewAssetIndex];
-				pushState(selectedAsset.id);
-			}
-		} catch (e) {
-			handleError(e, 'Cannot navigate to previous asset');
-		}
-	};
+  const navigateAssetBackward = () => {
+    try {
+      if (currentViewAssetIndex > 0) {
+        currentViewAssetIndex--;
+        selectedAsset = assets[currentViewAssetIndex];
+        pushState(selectedAsset.id);
+      }
+    } catch (e) {
+      handleError(e, 'Cannot navigate to previous asset');
+    }
+  };
 
-	const pushState = (assetId: string) => {
-		// add a URL to the browser's history
-		// changes the current URL in the address bar but doesn't perform any SvelteKit navigation
-		history.pushState(null, '', `${$page.url.pathname}/photos/${assetId}`);
-	};
+  const pushState = (assetId: string) => {
+    // add a URL to the browser's history
+    // changes the current URL in the address bar but doesn't perform any SvelteKit navigation
+    history.pushState(null, '', `${$page.url.pathname}/photos/${assetId}`);
+  };
 
-	const closeViewer = () => {
-		isShowAssetViewer = false;
-		history.pushState(null, '', `${$page.url.pathname}`);
-	};
+  const closeViewer = () => {
+    isShowAssetViewer = false;
+    history.pushState(null, '', `${$page.url.pathname}`);
+  };
 
-	const handleUnarchivedSuccess = (event: CustomEvent) => {
-		const asset = event.detail as AssetResponseDto;
-		switch (viewFrom) {
-			case 'archive-page':
-				$archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
-				navigateAssetForward();
-				break;
-		}
-	};
+  const handleUnarchivedSuccess = (event: CustomEvent) => {
+    const asset = event.detail as AssetResponseDto;
+    switch (viewFrom) {
+      case 'archive-page':
+        $archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
+        navigateAssetForward();
+        break;
+    }
+  };
 </script>
 
 {#if assets.length > 0}
-	<div class="flex flex-wrap gap-1 w-full pb-20" bind:clientWidth={viewWidth}>
-		{#each assets as asset (asset.id)}
-			<div animate:flip={{ duration: 500 }}>
-				<Thumbnail
-					{asset}
-					{thumbnailSize}
-					readonly={disableAssetSelect}
-					publicSharedKey={sharedLink?.key}
-					format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
-					on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
-					on:select={selectAssetHandler}
-					selected={selectedAssets.has(asset)}
-					{showArchiveIcon}
-				/>
-			</div>
-		{/each}
-	</div>
+  <div class="flex flex-wrap gap-1 w-full pb-20" bind:clientWidth={viewWidth}>
+    {#each assets as asset (asset.id)}
+      <div animate:flip={{ duration: 500 }}>
+        <Thumbnail
+          {asset}
+          {thumbnailSize}
+          readonly={disableAssetSelect}
+          publicSharedKey={sharedLink?.key}
+          format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
+          on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
+          on:select={selectAssetHandler}
+          selected={selectedAssets.has(asset)}
+          {showArchiveIcon}
+        />
+      </div>
+    {/each}
+  </div>
 {/if}
 
 <!-- Overlay Asset Viewer -->
 {#if isShowAssetViewer}
-	<AssetViewer
-		asset={selectedAsset}
-		publicSharedKey={sharedLink?.key}
-		{sharedLink}
-		on:navigate-previous={navigateAssetBackward}
-		on:navigate-next={navigateAssetForward}
-		on:close={closeViewer}
-		on:unarchived={handleUnarchivedSuccess}
-	/>
+  <AssetViewer
+    asset={selectedAsset}
+    publicSharedKey={sharedLink?.key}
+    {sharedLink}
+    on:navigate-previous={navigateAssetBackward}
+    on:navigate-next={navigateAssetForward}
+    on:close={closeViewer}
+    on:unarchived={handleUnarchivedSuccess}
+  />
 {/if}
diff --git a/web/src/lib/components/shared-components/immich-logo.svelte b/web/src/lib/components/shared-components/immich-logo.svelte
index c4bfa1c5ea..e3881c5b3b 100644
--- a/web/src/lib/components/shared-components/immich-logo.svelte
+++ b/web/src/lib/components/shared-components/immich-logo.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
-	import immichLogoUrl from '$lib/assets/immich-logo.svg';
+  import immichLogoUrl from '$lib/assets/immich-logo.svg';
 
-	export let draggable = false;
+  export let draggable = false;
 </script>
 
 <img src={immichLogoUrl} alt="Immich Logo" {draggable} {...$$restProps} />
diff --git a/web/src/lib/components/shared-components/leaflet/control.svelte b/web/src/lib/components/shared-components/leaflet/control.svelte
index 126a566d35..35767e7050 100644
--- a/web/src/lib/components/shared-components/leaflet/control.svelte
+++ b/web/src/lib/components/shared-components/leaflet/control.svelte
@@ -1,35 +1,35 @@
 <script lang="ts">
-	import { onDestroy, onMount } from 'svelte';
-	import { Control, type ControlPosition } from 'leaflet';
-	import { getMapContext } from './map.svelte';
+  import { onDestroy, onMount } from 'svelte';
+  import { Control, type ControlPosition } from 'leaflet';
+  import { getMapContext } from './map.svelte';
 
-	export let position: ControlPosition | undefined = undefined;
-	let className: string | undefined = undefined;
-	export { className as class };
+  export let position: ControlPosition | undefined = undefined;
+  let className: string | undefined = undefined;
+  export { className as class };
 
-	let control: Control;
-	let target: HTMLDivElement;
+  let control: Control;
+  let target: HTMLDivElement;
 
-	const map = getMapContext();
+  const map = getMapContext();
 
-	onMount(() => {
-		const ControlClass = Control.extend({
-			position,
-			onAdd: () => target
-		});
+  onMount(() => {
+    const ControlClass = Control.extend({
+      position,
+      onAdd: () => target,
+    });
 
-		control = new ControlClass().addTo(map);
-	});
+    control = new ControlClass().addTo(map);
+  });
 
-	onDestroy(() => {
-		control.remove();
-	});
+  onDestroy(() => {
+    control.remove();
+  });
 
-	$: if (control && position) {
-		control.setPosition(position);
-	}
+  $: if (control && position) {
+    control.setPosition(position);
+  }
 </script>
 
 <div bind:this={target} class={className}>
-	<slot />
+  <slot />
 </div>
diff --git a/web/src/lib/components/shared-components/leaflet/index.ts b/web/src/lib/components/shared-components/leaflet/index.ts
index 73248651aa..896c976e2a 100644
--- a/web/src/lib/components/shared-components/leaflet/index.ts
+++ b/web/src/lib/components/shared-components/leaflet/index.ts
@@ -1,5 +1,5 @@
-export { default as AssetMarkerCluster } from './marker-cluster/asset-marker-cluster.svelte';
 export { default as Control } from './control.svelte';
 export { default as Map } from './map.svelte';
+export { default as AssetMarkerCluster } from './marker-cluster/asset-marker-cluster.svelte';
 export { default as Marker } from './marker.svelte';
 export { default as TileLayer } from './tile-layer.svelte';
diff --git a/web/src/lib/components/shared-components/leaflet/map.svelte b/web/src/lib/components/shared-components/leaflet/map.svelte
index ad587c4371..994ca28ce7 100644
--- a/web/src/lib/components/shared-components/leaflet/map.svelte
+++ b/web/src/lib/components/shared-components/leaflet/map.svelte
@@ -1,50 +1,50 @@
 <script lang="ts" context="module">
-	import { createContext } from '$lib/utils/context';
+  import { createContext } from '$lib/utils/context';
 
-	const { get: getContext, set: setMapContext } = createContext<() => Map>();
+  const { get: getContext, set: setMapContext } = createContext<() => Map>();
 
-	export const getMapContext = () => {
-		const getMap = getContext();
-		return getMap();
-	};
+  export const getMapContext = () => {
+    const getMap = getContext();
+    return getMap();
+  };
 </script>
 
 <script lang="ts">
-	import { onMount, onDestroy } from 'svelte';
-	import { browser } from '$app/environment';
-	import { Map, type LatLngExpression, type MapOptions } from 'leaflet';
-	import 'leaflet/dist/leaflet.css';
+  import { onMount, onDestroy } from 'svelte';
+  import { browser } from '$app/environment';
+  import { Map, type LatLngExpression, type MapOptions } from 'leaflet';
+  import 'leaflet/dist/leaflet.css';
 
-	export let center: LatLngExpression;
-	export let zoom: number;
-	export let options: MapOptions | undefined = undefined;
-	export let allowDarkMode = false;
-	let container: HTMLDivElement;
-	let map: Map;
+  export let center: LatLngExpression;
+  export let zoom: number;
+  export let options: MapOptions | undefined = undefined;
+  export let allowDarkMode = false;
+  let container: HTMLDivElement;
+  let map: Map;
 
-	setMapContext(() => map);
+  setMapContext(() => map);
 
-	onMount(() => {
-		if (browser) {
-			map = new Map(container, options);
-		}
-	});
+  onMount(() => {
+    if (browser) {
+      map = new Map(container, options);
+    }
+  });
 
-	onDestroy(() => {
-		if (map) map.remove();
-	});
+  onDestroy(() => {
+    if (map) map.remove();
+  });
 
-	$: if (map) map.setView(center, zoom);
+  $: if (map) map.setView(center, zoom);
 </script>
 
 <div bind:this={container} class="w-full h-full" class:map-dark={allowDarkMode}>
-	{#if map}
-		<slot />
-	{/if}
+  {#if map}
+    <slot />
+  {/if}
 </div>
 
 <style>
-	:global(.dark) .map-dark :global(.leaflet-layer) {
-		filter: invert(100%) brightness(130%) saturate(0%);
-	}
+  :global(.dark) .map-dark :global(.leaflet-layer) {
+    filter: invert(100%) brightness(130%) saturate(0%);
+  }
 </style>
diff --git a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.css b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.css
index 4998aa363c..73f0abc11a 100644
--- a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.css
+++ b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.css
@@ -1,32 +1,31 @@
 .asset-marker-icon {
-	@apply rounded-full;
-	@apply object-cover;
-	@apply border;
-	@apply border-immich-primary;
-	@apply transition-all;
-	box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px,
-		rgba(0, 0, 0, 0.07) 0px 4px 8px, rgba(0, 0, 0, 0.07) 0px 8px 16px,
-		rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
+  @apply rounded-full;
+  @apply object-cover;
+  @apply border;
+  @apply border-immich-primary;
+  @apply transition-all;
+  box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px, rgba(0, 0, 0, 0.07) 0px 4px 8px,
+    rgba(0, 0, 0, 0.07) 0px 8px 16px, rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
 }
 
 .marker-cluster-icon {
-	@apply h-full;
-	@apply w-full;
-	@apply flex;
-	@apply justify-center;
-	@apply items-center;
-	@apply rounded-full;
-	@apply font-bold;
-	@apply bg-violet-50;
-	@apply border;
-	@apply border-immich-primary;
-	@apply text-immich-primary;
-	box-shadow: rgba(5, 5, 122, 0.12) 0px 2px 4px 0px, rgba(4, 4, 230, 0.32) 0px 2px 16px 0px;
+  @apply h-full;
+  @apply w-full;
+  @apply flex;
+  @apply justify-center;
+  @apply items-center;
+  @apply rounded-full;
+  @apply font-bold;
+  @apply bg-violet-50;
+  @apply border;
+  @apply border-immich-primary;
+  @apply text-immich-primary;
+  box-shadow: rgba(5, 5, 122, 0.12) 0px 2px 4px 0px, rgba(4, 4, 230, 0.32) 0px 2px 16px 0px;
 }
 
 .dark .map-dark .marker-cluster-icon {
-	@apply bg-blue-200;
-	@apply text-black;
-	@apply border-blue-200;
-	box-shadow: none;
+  @apply bg-blue-200;
+  @apply text-black;
+  @apply border-blue-200;
+  box-shadow: none;
 }
diff --git a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.svelte b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.svelte
index 15288b2a1f..4d408a5a7e 100644
--- a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.svelte
+++ b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker-cluster.svelte
@@ -1,104 +1,102 @@
 <script lang="ts" context="module">
-	import { createContext } from '$lib/utils/context';
-	import { MarkerClusterGroup } from 'leaflet';
+  import { createContext } from '$lib/utils/context';
+  import { MarkerClusterGroup } from 'leaflet';
 
-	const { get: getContext, set: setClusterContext } = createContext<() => MarkerClusterGroup>();
+  const { get: getContext, set: setClusterContext } = createContext<() => MarkerClusterGroup>();
 
-	export const getClusterContext = () => {
-		return getContext()();
-	};
+  export const getClusterContext = () => {
+    return getContext()();
+  };
 </script>
 
 <script lang="ts">
-	import type { MapMarkerResponseDto } from '@api';
-	import { DivIcon, LeafletEvent, LeafletMouseEvent, MarkerCluster, Point } from 'leaflet';
-	import 'leaflet.markercluster';
-	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
-	import { getMapContext } from '../map.svelte';
-	import AssetMarker from './asset-marker';
-	import './asset-marker-cluster.css';
+  import type { MapMarkerResponseDto } from '@api';
+  import { DivIcon, LeafletEvent, LeafletMouseEvent, MarkerCluster, Point } from 'leaflet';
+  import 'leaflet.markercluster';
+  import { createEventDispatcher, onDestroy, onMount } from 'svelte';
+  import { getMapContext } from '../map.svelte';
+  import AssetMarker from './asset-marker';
+  import './asset-marker-cluster.css';
 
-	export let markers: MapMarkerResponseDto[];
-	export let spiderfyLimit = 10;
-	let cluster: MarkerClusterGroup;
+  export let markers: MapMarkerResponseDto[];
+  export let spiderfyLimit = 10;
+  let cluster: MarkerClusterGroup;
 
-	const map = getMapContext();
-	const dispatch = createEventDispatcher<{
-		view: { assetIds: string[]; activeAssetIndex: number };
-	}>();
+  const map = getMapContext();
+  const dispatch = createEventDispatcher<{
+    view: { assetIds: string[]; activeAssetIndex: number };
+  }>();
 
-	setClusterContext(() => cluster);
+  setClusterContext(() => cluster);
 
-	onMount(() => {
-		cluster = new MarkerClusterGroup({
-			showCoverageOnHover: false,
-			zoomToBoundsOnClick: false,
-			spiderfyOnMaxZoom: false,
-			maxClusterRadius: (zoom) => 80 - zoom * 2,
-			spiderLegPolylineOptions: { opacity: 0 },
-			spiderfyDistanceMultiplier: 3,
-			iconCreateFunction: (options) => {
-				const childCount = options.getChildCount();
-				const iconSize = childCount > spiderfyLimit ? 45 : 40;
+  onMount(() => {
+    cluster = new MarkerClusterGroup({
+      showCoverageOnHover: false,
+      zoomToBoundsOnClick: false,
+      spiderfyOnMaxZoom: false,
+      maxClusterRadius: (zoom) => 80 - zoom * 2,
+      spiderLegPolylineOptions: { opacity: 0 },
+      spiderfyDistanceMultiplier: 3,
+      iconCreateFunction: (options) => {
+        const childCount = options.getChildCount();
+        const iconSize = childCount > spiderfyLimit ? 45 : 40;
 
-				return new DivIcon({
-					html: `<div class="marker-cluster-icon">${childCount}</div>`,
-					className: '',
-					iconSize: new Point(iconSize, iconSize)
-				});
-			}
-		});
+        return new DivIcon({
+          html: `<div class="marker-cluster-icon">${childCount}</div>`,
+          className: '',
+          iconSize: new Point(iconSize, iconSize),
+        });
+      },
+    });
 
-		cluster.on('clusterclick', (event: LeafletEvent) => {
-			const markerCluster: MarkerCluster = event.sourceTarget;
-			const childCount = markerCluster.getChildCount();
+    cluster.on('clusterclick', (event: LeafletEvent) => {
+      const markerCluster: MarkerCluster = event.sourceTarget;
+      const childCount = markerCluster.getChildCount();
 
-			if (childCount > spiderfyLimit) {
-				const markers = markerCluster.getAllChildMarkers() as AssetMarker[];
-				onView(markers, markers[0].id);
-			} else {
-				markerCluster.spiderfy();
-			}
-		});
+      if (childCount > spiderfyLimit) {
+        const markers = markerCluster.getAllChildMarkers() as AssetMarker[];
+        onView(markers, markers[0].id);
+      } else {
+        markerCluster.spiderfy();
+      }
+    });
 
-		cluster.on('click', (event: LeafletMouseEvent) => {
-			const marker: AssetMarker = event.sourceTarget;
-			const markerCluster = getClusterByMarker(marker);
-			const markers = markerCluster
-				? (markerCluster.getAllChildMarkers() as AssetMarker[])
-				: [marker];
+    cluster.on('click', (event: LeafletMouseEvent) => {
+      const marker: AssetMarker = event.sourceTarget;
+      const markerCluster = getClusterByMarker(marker);
+      const markers = markerCluster ? (markerCluster.getAllChildMarkers() as AssetMarker[]) : [marker];
 
-			onView(markers, marker.id);
-		});
+      onView(markers, marker.id);
+    });
 
-		map.addLayer(cluster);
-	});
+    map.addLayer(cluster);
+  });
 
-	/* eslint-disable-next-line  @typescript-eslint/no-explicit-any */
-	const getClusterByMarker = (marker: any): MarkerCluster | undefined => {
-		const mapZoom = map.getZoom();
+  /* eslint-disable-next-line  @typescript-eslint/no-explicit-any */
+  const getClusterByMarker = (marker: any): MarkerCluster | undefined => {
+    const mapZoom = map.getZoom();
 
-		while (marker && marker._zoom !== mapZoom) {
-			marker = marker.__parent;
-		}
+    while (marker && marker._zoom !== mapZoom) {
+      marker = marker.__parent;
+    }
 
-		return marker;
-	};
+    return marker;
+  };
 
-	const onView = (markers: AssetMarker[], activeAssetId: string) => {
-		const assetIds = markers.map((marker) => marker.id);
-		const activeAssetIndex = assetIds.indexOf(activeAssetId) || 0;
-		dispatch('view', { assetIds, activeAssetIndex });
-	};
+  const onView = (markers: AssetMarker[], activeAssetId: string) => {
+    const assetIds = markers.map((marker) => marker.id);
+    const activeAssetIndex = assetIds.indexOf(activeAssetId) || 0;
+    dispatch('view', { assetIds, activeAssetIndex });
+  };
 
-	$: if (cluster) {
-		const leafletMarkers = markers.map((marker) => new AssetMarker(marker));
+  $: if (cluster) {
+    const leafletMarkers = markers.map((marker) => new AssetMarker(marker));
 
-		cluster.clearLayers();
-		cluster.addLayers(leafletMarkers);
-	}
+    cluster.clearLayers();
+    cluster.addLayers(leafletMarkers);
+  }
 
-	onDestroy(() => {
-		if (cluster) cluster.remove();
-	});
+  onDestroy(() => {
+    if (cluster) cluster.remove();
+  });
 </script>
diff --git a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker.ts b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker.ts
index 38e6299b92..c5ded9c936 100644
--- a/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker.ts
+++ b/web/src/lib/components/shared-components/leaflet/marker-cluster/asset-marker.ts
@@ -1,37 +1,37 @@
-import { MapMarkerResponseDto, api } from '@api';
-import { Marker, Map, Icon } from 'leaflet';
+import { api, MapMarkerResponseDto } from '@api';
+import { Icon, Map, Marker } from 'leaflet';
 
 export default class AssetMarker extends Marker {
-	id: string;
-	private iconCreated = false;
+  id: string;
+  private iconCreated = false;
 
-	constructor(marker: MapMarkerResponseDto) {
-		super([marker.lat, marker.lon]);
-		this.id = marker.id;
-	}
+  constructor(marker: MapMarkerResponseDto) {
+    super([marker.lat, marker.lon]);
+    this.id = marker.id;
+  }
 
-	onAdd(map: Map) {
-		// Set icon when the marker gets actually added to the map. This only
-		// gets called for individual assets and when selecting a cluster, so
-		// creating an icon for every marker in advance is pretty wasteful.
-		if (!this.iconCreated) {
-			this.iconCreated = true;
-			this.setIcon(this.getIcon());
-		}
+  onAdd(map: Map) {
+    // Set icon when the marker gets actually added to the map. This only
+    // gets called for individual assets and when selecting a cluster, so
+    // creating an icon for every marker in advance is pretty wasteful.
+    if (!this.iconCreated) {
+      this.iconCreated = true;
+      this.setIcon(this.getIcon());
+    }
 
-		return super.onAdd(map);
-	}
+    return super.onAdd(map);
+  }
 
-	getIcon() {
-		return new Icon({
-			iconUrl: api.getAssetThumbnailUrl(this.id),
-			iconRetinaUrl: api.getAssetThumbnailUrl(this.id),
-			iconSize: [60, 60],
-			iconAnchor: [12, 41],
-			popupAnchor: [1, -34],
-			tooltipAnchor: [16, -28],
-			shadowSize: [41, 41],
-			className: 'asset-marker-icon'
-		});
-	}
+  getIcon() {
+    return new Icon({
+      iconUrl: api.getAssetThumbnailUrl(this.id),
+      iconRetinaUrl: api.getAssetThumbnailUrl(this.id),
+      iconSize: [60, 60],
+      iconAnchor: [12, 41],
+      popupAnchor: [1, -34],
+      tooltipAnchor: [16, -28],
+      shadowSize: [41, 41],
+      className: 'asset-marker-icon',
+    });
+  }
 }
diff --git a/web/src/lib/components/shared-components/leaflet/marker.svelte b/web/src/lib/components/shared-components/leaflet/marker.svelte
index 598bb9f111..dac3967037 100644
--- a/web/src/lib/components/shared-components/leaflet/marker.svelte
+++ b/web/src/lib/components/shared-components/leaflet/marker.svelte
@@ -1,46 +1,46 @@
 <script lang="ts">
-	import { onDestroy, onMount } from 'svelte';
-	import { Marker, Icon, type LatLngExpression, type Content } from 'leaflet';
-	import { getMapContext } from './map.svelte';
-	import iconUrl from 'leaflet/dist/images/marker-icon.png';
-	import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
-	import shadowUrl from 'leaflet/dist/images/marker-shadow.png';
+  import { onDestroy, onMount } from 'svelte';
+  import { Marker, Icon, type LatLngExpression, type Content } from 'leaflet';
+  import { getMapContext } from './map.svelte';
+  import iconUrl from 'leaflet/dist/images/marker-icon.png';
+  import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
+  import shadowUrl from 'leaflet/dist/images/marker-shadow.png';
 
-	export let latlng: LatLngExpression;
-	export let popupContent: Content | undefined = undefined;
-	let marker: Marker;
+  export let latlng: LatLngExpression;
+  export let popupContent: Content | undefined = undefined;
+  let marker: Marker;
 
-	const defaultIcon = new Icon({
-		iconUrl,
-		iconRetinaUrl,
-		shadowUrl,
+  const defaultIcon = new Icon({
+    iconUrl,
+    iconRetinaUrl,
+    shadowUrl,
 
-		// Default values from Leaflet
-		iconSize: [25, 41],
-		iconAnchor: [12, 41],
-		popupAnchor: [1, -34],
-		tooltipAnchor: [16, -28],
-		shadowSize: [41, 41]
-	});
-	const map = getMapContext();
+    // Default values from Leaflet
+    iconSize: [25, 41],
+    iconAnchor: [12, 41],
+    popupAnchor: [1, -34],
+    tooltipAnchor: [16, -28],
+    shadowSize: [41, 41],
+  });
+  const map = getMapContext();
 
-	onMount(() => {
-		marker = new Marker(latlng, {
-			icon: defaultIcon
-		}).addTo(map);
-	});
+  onMount(() => {
+    marker = new Marker(latlng, {
+      icon: defaultIcon,
+    }).addTo(map);
+  });
 
-	onDestroy(() => {
-		if (marker) marker.remove();
-	});
+  onDestroy(() => {
+    if (marker) marker.remove();
+  });
 
-	$: if (marker) {
-		marker.setLatLng(latlng);
+  $: if (marker) {
+    marker.setLatLng(latlng);
 
-		if (popupContent) {
-			marker.bindPopup(popupContent);
-		} else {
-			marker.unbindPopup();
-		}
-	}
+    if (popupContent) {
+      marker.bindPopup(popupContent);
+    } else {
+      marker.unbindPopup();
+    }
+  }
 </script>
diff --git a/web/src/lib/components/shared-components/leaflet/tile-layer.svelte b/web/src/lib/components/shared-components/leaflet/tile-layer.svelte
index 60a5662eb5..facc32cb28 100644
--- a/web/src/lib/components/shared-components/leaflet/tile-layer.svelte
+++ b/web/src/lib/components/shared-components/leaflet/tile-layer.svelte
@@ -1,20 +1,20 @@
 <script lang="ts">
-	import { onDestroy, onMount } from 'svelte';
-	import { TileLayer, type TileLayerOptions } from 'leaflet';
-	import { getMapContext } from './map.svelte';
+  import { onDestroy, onMount } from 'svelte';
+  import { TileLayer, type TileLayerOptions } from 'leaflet';
+  import { getMapContext } from './map.svelte';
 
-	export let urlTemplate: string;
-	export let options: TileLayerOptions | undefined = undefined;
+  export let urlTemplate: string;
+  export let options: TileLayerOptions | undefined = undefined;
 
-	let tileLayer: TileLayer;
+  let tileLayer: TileLayer;
 
-	const map = getMapContext();
+  const map = getMapContext();
 
-	onMount(() => {
-		tileLayer = new TileLayer(urlTemplate, options).addTo(map);
-	});
+  onMount(() => {
+    tileLayer = new TileLayer(urlTemplate, options).addTo(map);
+  });
 
-	onDestroy(() => {
-		if (tileLayer) tileLayer.remove();
-	});
+  onDestroy(() => {
+    if (tileLayer) tileLayer.remove();
+  });
 </script>
diff --git a/web/src/lib/components/shared-components/loading-spinner.svelte b/web/src/lib/components/shared-components/loading-spinner.svelte
index 6dcd23b6dc..a81194db82 100644
--- a/web/src/lib/components/shared-components/loading-spinner.svelte
+++ b/web/src/lib/components/shared-components/loading-spinner.svelte
@@ -1,18 +1,18 @@
 <div>
-	<svg
-		role="status"
-		class={`w-[24px] h-[24px] text-gray-400 animate-spin dark:text-gray-600 fill-immich-primary`}
-		viewBox="0 0 100 101"
-		fill="none"
-		xmlns="http://www.w3.org/2000/svg"
-	>
-		<path
-			d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
-			fill="currentColor"
-		/>
-		<path
-			d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
-			fill="currentFill"
-		/>
-	</svg>
+  <svg
+    role="status"
+    class={`w-[24px] h-[24px] text-gray-400 animate-spin dark:text-gray-600 fill-immich-primary`}
+    viewBox="0 0 100 101"
+    fill="none"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
+      fill="currentColor"
+    />
+    <path
+      d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
+      fill="currentFill"
+    />
+  </svg>
 </div>
diff --git a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte
index 957eed2034..41ac5c21b2 100644
--- a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte
@@ -1,54 +1,54 @@
 <script lang="ts">
-	import Button from '$lib/components/elements/buttons/button.svelte';
-	import { AppRoute } from '$lib/constants';
-	import type { UserResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-	import Cog from 'svelte-material-icons/Cog.svelte';
-	import Logout from 'svelte-material-icons/Logout.svelte';
-	import { fade } from 'svelte/transition';
-	import UserAvatar from '../user-avatar.svelte';
+  import Button from '$lib/components/elements/buttons/button.svelte';
+  import { AppRoute } from '$lib/constants';
+  import type { UserResponseDto } from '@api';
+  import { createEventDispatcher } from 'svelte';
+  import Cog from 'svelte-material-icons/Cog.svelte';
+  import Logout from 'svelte-material-icons/Logout.svelte';
+  import { fade } from 'svelte/transition';
+  import UserAvatar from '../user-avatar.svelte';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 </script>
 
 <div
-	in:fade={{ duration: 100 }}
-	out:fade={{ duration: 100 }}
-	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] z-[100]"
+  in:fade={{ duration: 100 }}
+  out:fade={{ duration: 100 }}
+  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] z-[100]"
 >
-	<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
+    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 text-center">
-				{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 text-center">
+        {user.firstName}
+        {user.lastName}
+      </p>
+      <p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
+    </div>
 
-		<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">
-					<Cog size="18" />
-					Account Settings
-				</div>
-			</Button>
-		</a>
-	</div>
+    <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">
+          <Cog size="18" />
+          Account Settings
+        </div>
+      </Button>
+    </a>
+  </div>
 
-	<div class="mb-4 flex flex-col">
-		<button
-			class="py-3 w-full font-medium flex place-items-center gap-2 hover:bg-immich-primary/10 text-gray-500 dark:text-gray-300 place-content-center"
-			on:click={() => dispatch('logout')}
-		>
-			<Logout size={24} />
-			Sign Out</button
-		>
-	</div>
+  <div class="mb-4 flex flex-col">
+    <button
+      class="py-3 w-full font-medium flex place-items-center gap-2 hover:bg-immich-primary/10 text-gray-500 dark:text-gray-300 place-content-center"
+      on:click={() => dispatch('logout')}
+    >
+      <Logout size={24} />
+      Sign Out</button
+    >
+  </div>
 </div>
diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
index f7eac2bf2d..a4f4ac4fd3 100644
--- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
@@ -1,147 +1,141 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import { page } from '$app/stores';
-	import { clickOutside } from '$lib/utils/click-outside';
-	import { createEventDispatcher } from 'svelte';
-	import { fade, fly } from 'svelte/transition';
-	import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
-	import { api, UserResponseDto } from '@api';
-	import ThemeButton from '../theme-button.svelte';
-	import { AppRoute } from '../../../constants';
-	import AccountInfoPanel from './account-info-panel.svelte';
-	import ImmichLogo from '../immich-logo.svelte';
-	import SearchBar from '../search-bar/search-bar.svelte';
-	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
-	import Magnify from 'svelte-material-icons/Magnify.svelte';
-	import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
-	import Cog from 'svelte-material-icons/Cog.svelte';
-	import UserAvatar from '../user-avatar.svelte';
-	export let user: UserResponseDto;
-	export let showUploadButton = true;
+  import { goto } from '$app/navigation';
+  import { page } from '$app/stores';
+  import { clickOutside } from '$lib/utils/click-outside';
+  import { createEventDispatcher } from 'svelte';
+  import { fade, fly } from 'svelte/transition';
+  import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
+  import { api, UserResponseDto } from '@api';
+  import ThemeButton from '../theme-button.svelte';
+  import { AppRoute } from '../../../constants';
+  import AccountInfoPanel from './account-info-panel.svelte';
+  import ImmichLogo from '../immich-logo.svelte';
+  import SearchBar from '../search-bar/search-bar.svelte';
+  import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
+  import Magnify from 'svelte-material-icons/Magnify.svelte';
+  import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
+  import Cog from 'svelte-material-icons/Cog.svelte';
+  import UserAvatar from '../user-avatar.svelte';
+  export let user: UserResponseDto;
+  export let showUploadButton = true;
 
-	let shouldShowAccountInfo = false;
-	let shouldShowAccountInfoPanel = false;
+  let shouldShowAccountInfo = false;
+  let shouldShowAccountInfoPanel = false;
 
-	const dispatch = createEventDispatcher();
+  const dispatch = createEventDispatcher();
 
-	const logOut = async () => {
-		const { data } = await api.authenticationApi.logout();
+  const logOut = async () => {
+    const { data } = await api.authenticationApi.logout();
 
-		await fetch('/auth/logout', { method: 'POST' });
+    await fetch('/auth/logout', { method: 'POST' });
 
-		goto(data.redirectUri || '/auth/login?autoLaunch=0');
-	};
+    goto(data.redirectUri || '/auth/login?autoLaunch=0');
+  };
 </script>
 
 <section id="dashboard-navbar" class="fixed h-[var(--navbar-height)] w-screen z-[900] text-sm">
-	<div
-		class="grid h-full md:grid-cols-[theme(spacing.64)_auto] grid-cols-[theme(spacing.18)_auto] border-b dark:border-b-immich-dark-gray items-center py-2 bg-immich-bg dark:bg-immich-dark-bg"
-	>
-		<a
-			data-sveltekit-preload-data="hover"
-			class="flex gap-2 md:mx-6 mx-4 place-items-center"
-			href={AppRoute.PHOTOS}
-		>
-			<ImmichLogo height="35" width="35" />
-			<h1
-				class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary md:block hidden"
-			>
-				IMMICH
-			</h1>
-		</a>
-		<div class="flex justify-between gap-16 pr-6">
-			<div class="w-full max-w-5xl flex-1 pl-4 sm:block hidden">
-				<SearchBar grayTheme={true} />
-			</div>
+  <div
+    class="grid h-full md:grid-cols-[theme(spacing.64)_auto] grid-cols-[theme(spacing.18)_auto] border-b dark:border-b-immich-dark-gray items-center py-2 bg-immich-bg dark:bg-immich-dark-bg"
+  >
+    <a data-sveltekit-preload-data="hover" class="flex gap-2 md:mx-6 mx-4 place-items-center" href={AppRoute.PHOTOS}>
+      <ImmichLogo height="35" width="35" />
+      <h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary md:block hidden">
+        IMMICH
+      </h1>
+    </a>
+    <div class="flex justify-between gap-16 pr-6">
+      <div class="w-full max-w-5xl flex-1 pl-4 sm:block hidden">
+        <SearchBar grayTheme={true} />
+      </div>
 
-			<section class="flex gap-4 place-items-center justify-end max-sm:w-full">
-				<a href={AppRoute.SEARCH} id="search-button" class="sm:hidden pl-4">
-					<IconButton title="Search">
-						<div class="flex gap-2">
-							<Magnify size="1.5em" />
-						</div>
-					</IconButton>
-				</a>
+      <section class="flex gap-4 place-items-center justify-end max-sm:w-full">
+        <a href={AppRoute.SEARCH} id="search-button" class="sm:hidden pl-4">
+          <IconButton title="Search">
+            <div class="flex gap-2">
+              <Magnify size="1.5em" />
+            </div>
+          </IconButton>
+        </a>
 
-				<ThemeButton />
+        <ThemeButton />
 
-				{#if !$page.url.pathname.includes('/admin') && showUploadButton}
-					<div in:fly={{ x: 50, duration: 250 }}>
-						<LinkButton on:click={() => dispatch('uploadClicked')}>
-							<div class="flex gap-2">
-								<TrayArrowUp size="1.5em" />
-								<span class="md:block hidden">Upload</span>
-							</div>
-						</LinkButton>
-					</div>
-				{/if}
+        {#if !$page.url.pathname.includes('/admin') && showUploadButton}
+          <div in:fly={{ x: 50, duration: 250 }}>
+            <LinkButton on:click={() => dispatch('uploadClicked')}>
+              <div class="flex gap-2">
+                <TrayArrowUp size="1.5em" />
+                <span class="md:block hidden">Upload</span>
+              </div>
+            </LinkButton>
+          </div>
+        {/if}
 
-				{#if user.isAdmin}
-					<a data-sveltekit-preload-data="hover" href={AppRoute.ADMIN_USER_MANAGEMENT}>
-						<div class="sm:block hidden">
-							<LinkButton>
-								<span
-									class={$page.url.pathname.includes('/admin')
-										? 'text-immich-primary dark:text-immich-dark-primary underline item'
-										: ''}
-								>
-									Administration
-								</span>
-							</LinkButton>
-						</div>
-						<div class="sm:hidden block">
-							<IconButton title="Administration">
-								<Cog
-									size="1.5em"
-									class="dark:text-immich-dark-fg {$page.url.pathname.includes('/admin')
-										? 'text-immich-primary dark:text-immich-dark-primary'
-										: ''}"
-								/>
-							</IconButton>
-							<hr
-								class={$page.url.pathname.includes('/admin')
-									? 'block border-1 w-2/3 mx-auto border-immich-primary dark:border-immich-dark-primary'
-									: 'hidden'}
-							/>
-						</div>
-					</a>
-				{/if}
+        {#if user.isAdmin}
+          <a data-sveltekit-preload-data="hover" href={AppRoute.ADMIN_USER_MANAGEMENT}>
+            <div class="sm:block hidden">
+              <LinkButton>
+                <span
+                  class={$page.url.pathname.includes('/admin')
+                    ? 'text-immich-primary dark:text-immich-dark-primary underline item'
+                    : ''}
+                >
+                  Administration
+                </span>
+              </LinkButton>
+            </div>
+            <div class="sm:hidden block">
+              <IconButton title="Administration">
+                <Cog
+                  size="1.5em"
+                  class="dark:text-immich-dark-fg {$page.url.pathname.includes('/admin')
+                    ? 'text-immich-primary dark:text-immich-dark-primary'
+                    : ''}"
+                />
+              </IconButton>
+              <hr
+                class={$page.url.pathname.includes('/admin')
+                  ? 'block border-1 w-2/3 mx-auto border-immich-primary dark:border-immich-dark-primary'
+                  : 'hidden'}
+              />
+            </div>
+          </a>
+        {/if}
 
-				<div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
-					<button
-						class="flex"
-						on:mouseover={() => (shouldShowAccountInfo = true)}
-						on:focus={() => (shouldShowAccountInfo = true)}
-						on:blur={() => (shouldShowAccountInfo = false)}
-						on:mouseleave={() => (shouldShowAccountInfo = false)}
-						on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
-					>
-						<UserAvatar {user} size="md" showTitle={false} interactive />
-					</button>
+        <div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
+          <button
+            class="flex"
+            on:mouseover={() => (shouldShowAccountInfo = true)}
+            on:focus={() => (shouldShowAccountInfo = true)}
+            on:blur={() => (shouldShowAccountInfo = false)}
+            on:mouseleave={() => (shouldShowAccountInfo = false)}
+            on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
+          >
+            <UserAvatar {user} size="md" showTitle={false} interactive />
+          </button>
 
-					{#if shouldShowAccountInfo && !shouldShowAccountInfoPanel}
-						<div
-							in:fade={{ delay: 500, duration: 150 }}
-							out:fade={{ delay: 200, duration: 150 }}
-							class="absolute -bottom-12 right-5 border bg-gray-500 dark:bg-immich-dark-gray text-[12px] text-gray-100 p-2 rounded-md shadow-md dark:border-immich-dark-gray"
-						>
-							<p>{user.firstName} {user.lastName}</p>
-							<p>{user.email}</p>
-						</div>
-					{/if}
+          {#if shouldShowAccountInfo && !shouldShowAccountInfoPanel}
+            <div
+              in:fade={{ delay: 500, duration: 150 }}
+              out:fade={{ delay: 200, duration: 150 }}
+              class="absolute -bottom-12 right-5 border bg-gray-500 dark:bg-immich-dark-gray text-[12px] text-gray-100 p-2 rounded-md shadow-md dark:border-immich-dark-gray"
+            >
+              <p>{user.firstName} {user.lastName}</p>
+              <p>{user.email}</p>
+            </div>
+          {/if}
 
-					{#if shouldShowAccountInfoPanel}
-						<AccountInfoPanel {user} on:logout={logOut} />
-					{/if}
-				</div>
-			</section>
-		</div>
-	</div>
+          {#if shouldShowAccountInfoPanel}
+            <AccountInfoPanel {user} on:logout={logOut} />
+          {/if}
+        </div>
+      </section>
+    </div>
+  </div>
 </section>
 
 <style>
-	:root {
-		/* Used by layouts to ensure proper spacing between navbar and content */
-		--navbar-height: calc(theme(spacing.18) + 4px);
-	}
+  :root {
+    /* Used by layouts to ensure proper spacing between navbar and content */
+    --navbar-height: calc(theme(spacing.18) + 4px);
+  }
 </style>
diff --git a/web/src/lib/components/shared-components/navigation-loading-bar.svelte b/web/src/lib/components/shared-components/navigation-loading-bar.svelte
index c4f7f63b0c..bfca26adad 100644
--- a/web/src/lib/components/shared-components/navigation-loading-bar.svelte
+++ b/web/src/lib/components/shared-components/navigation-loading-bar.svelte
@@ -1,18 +1,18 @@
 <script lang="ts">
-	import { onMount } from 'svelte';
-	import { cubicOut } from 'svelte/easing';
-	import { tweened } from 'svelte/motion';
+  import { onMount } from 'svelte';
+  import { cubicOut } from 'svelte/easing';
+  import { tweened } from 'svelte/motion';
 
-	const progress = tweened(0, {
-		duration: 1000,
-		easing: cubicOut
-	});
+  const progress = tweened(0, {
+    duration: 1000,
+    easing: cubicOut,
+  });
 
-	onMount(() => {
-		progress.set(90);
-	});
+  onMount(() => {
+    progress.set(90);
+  });
 </script>
 
 <div class="absolute top-0 left-0 w-screen h-[3px] bg-white z-[999999999]">
-	<span class="absolute bg-immich-primary h-[3px]" style:width={`${$progress}%`} />
+  <span class="absolute bg-immich-primary h-[3px]" style:width={`${$progress}%`} />
 </div>
diff --git a/web/src/lib/components/shared-components/notification/__tests__/notification-card.spec.ts b/web/src/lib/components/shared-components/notification/__tests__/notification-card.spec.ts
index 551f4fd230..dd1017ea36 100644
--- a/web/src/lib/components/shared-components/notification/__tests__/notification-card.spec.ts
+++ b/web/src/lib/components/shared-components/notification/__tests__/notification-card.spec.ts
@@ -1,41 +1,41 @@
-import { jest, describe, it } from '@jest/globals';
-import { render, cleanup, RenderResult } from '@testing-library/svelte';
+import { describe, it, jest } from '@jest/globals';
+import '@testing-library/jest-dom';
+import { cleanup, render, RenderResult } from '@testing-library/svelte';
 import { NotificationType } from '../notification';
 import NotificationCard from '../notification-card.svelte';
-import '@testing-library/jest-dom';
 
 describe('NotificationCard component', () => {
-	let sut: RenderResult<NotificationCard>;
+  let sut: RenderResult<NotificationCard>;
 
-	it('disposes timeout if already removed from the DOM', () => {
-		jest.spyOn(window, 'clearTimeout');
+  it('disposes timeout if already removed from the DOM', () => {
+    jest.spyOn(window, 'clearTimeout');
 
-		sut = render(NotificationCard, {
-			notificationInfo: {
-				id: 1234,
-				message: 'Notification message',
-				timeout: 1000,
-				type: NotificationType.Info,
-				action: { type: 'discard' }
-			}
-		});
+    sut = render(NotificationCard, {
+      notificationInfo: {
+        id: 1234,
+        message: 'Notification message',
+        timeout: 1000,
+        type: NotificationType.Info,
+        action: { type: 'discard' },
+      },
+    });
 
-		cleanup();
-		expect(window.clearTimeout).toHaveBeenCalledTimes(1);
-	});
+    cleanup();
+    expect(window.clearTimeout).toHaveBeenCalledTimes(1);
+  });
 
-	it('shows message and title', () => {
-		sut = render(NotificationCard, {
-			notificationInfo: {
-				id: 1234,
-				message: 'Notification message',
-				timeout: 1000,
-				type: NotificationType.Info,
-				action: { type: 'discard' }
-			}
-		});
+  it('shows message and title', () => {
+    sut = render(NotificationCard, {
+      notificationInfo: {
+        id: 1234,
+        message: 'Notification message',
+        timeout: 1000,
+        type: NotificationType.Info,
+        action: { type: 'discard' },
+      },
+    });
 
-		expect(sut.getByTestId('title')).toHaveTextContent('Info');
-		expect(sut.getByTestId('message')).toHaveTextContent('Notification message');
-	});
+    expect(sut.getByTestId('title')).toHaveTextContent('Info');
+    expect(sut.getByTestId('message')).toHaveTextContent('Notification message');
+  });
 });
diff --git a/web/src/lib/components/shared-components/notification/__tests__/notification-list.spec.ts b/web/src/lib/components/shared-components/notification/__tests__/notification-list.spec.ts
index 84ab734033..1ab5cd18c2 100644
--- a/web/src/lib/components/shared-components/notification/__tests__/notification-list.spec.ts
+++ b/web/src/lib/components/shared-components/notification/__tests__/notification-list.spec.ts
@@ -1,44 +1,42 @@
-import { jest, describe, it } from '@jest/globals';
-import { render, RenderResult, waitFor } from '@testing-library/svelte';
-import { notificationController, NotificationType } from '../notification';
-import { get } from 'svelte/store';
-import NotificationList from '../notification-list.svelte';
+import { describe, it, jest } from '@jest/globals';
 import '@testing-library/jest-dom';
+import { render, RenderResult, waitFor } from '@testing-library/svelte';
+import { get } from 'svelte/store';
+import { notificationController, NotificationType } from '../notification';
+import NotificationList from '../notification-list.svelte';
 
-function _getNotificationListElement(
-	sut: RenderResult<NotificationList>
-): HTMLAnchorElement | null {
-	return sut.container.querySelector('#notification-list');
+function _getNotificationListElement(sut: RenderResult<NotificationList>): HTMLAnchorElement | null {
+  return sut.container.querySelector('#notification-list');
 }
 
 describe('NotificationList component', () => {
-	const sut: RenderResult<NotificationList> = render(NotificationList);
+  const sut: RenderResult<NotificationList> = render(NotificationList);
 
-	beforeAll(() => {
-		jest.useFakeTimers();
-	});
+  beforeAll(() => {
+    jest.useFakeTimers();
+  });
 
-	afterAll(() => {
-		jest.useRealTimers();
-	});
+  afterAll(() => {
+    jest.useRealTimers();
+  });
 
-	it('shows a notification when added and closes it automatically after the delay timeout', async () => {
-		expect(_getNotificationListElement(sut)).not.toBeInTheDocument();
+  it('shows a notification when added and closes it automatically after the delay timeout', async () => {
+    expect(_getNotificationListElement(sut)).not.toBeInTheDocument();
 
-		notificationController.show({
-			message: 'Notification',
-			type: NotificationType.Info,
-			timeout: 3000
-		});
+    notificationController.show({
+      message: 'Notification',
+      type: NotificationType.Info,
+      timeout: 3000,
+    });
 
-		await waitFor(() => expect(_getNotificationListElement(sut)).toBeInTheDocument());
+    await waitFor(() => expect(_getNotificationListElement(sut)).toBeInTheDocument());
 
-		expect(_getNotificationListElement(sut)?.children).toHaveLength(1);
+    expect(_getNotificationListElement(sut)?.children).toHaveLength(1);
 
-		jest.advanceTimersByTime(3000);
-		// due to some weirdness in svelte (or testing-library) need to check if it has been removed from the store to make sure it works.
-		expect(get(notificationController.notificationList)).toHaveLength(0);
+    jest.advanceTimersByTime(3000);
+    // due to some weirdness in svelte (or testing-library) need to check if it has been removed from the store to make sure it works.
+    expect(get(notificationController.notificationList)).toHaveLength(0);
 
-		await waitFor(() => expect(_getNotificationListElement(sut)).not.toBeInTheDocument());
-	});
+    await waitFor(() => expect(_getNotificationListElement(sut)).not.toBeInTheDocument());
+  });
 });
diff --git a/web/src/lib/components/shared-components/notification/notification-card.svelte b/web/src/lib/components/shared-components/notification/notification-card.svelte
index 5bce6f6928..1c11b25974 100644
--- a/web/src/lib/components/shared-components/notification/notification-card.svelte
+++ b/web/src/lib/components/shared-components/notification/notification-card.svelte
@@ -1,96 +1,95 @@
 <script lang="ts">
-	import { fade } from 'svelte/transition';
-	import CloseCircleOutline from 'svelte-material-icons/CloseCircleOutline.svelte';
-	import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
-	import WindowClose from 'svelte-material-icons/WindowClose.svelte';
+  import { fade } from 'svelte/transition';
+  import CloseCircleOutline from 'svelte-material-icons/CloseCircleOutline.svelte';
+  import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
+  import WindowClose from 'svelte-material-icons/WindowClose.svelte';
 
-	import {
-		ImmichNotification,
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { onMount } from 'svelte';
+  import {
+    ImmichNotification,
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { onMount } from 'svelte';
 
-	export let notificationInfo: ImmichNotification;
+  export let notificationInfo: ImmichNotification;
 
-	let infoPrimaryColor = '#4250AF';
-	let errorPrimaryColor = '#E64132';
+  let infoPrimaryColor = '#4250AF';
+  let errorPrimaryColor = '#E64132';
 
-	$: icon =
-		notificationInfo.type === NotificationType.Error ? CloseCircleOutline : InformationOutline;
+  $: icon = notificationInfo.type === NotificationType.Error ? CloseCircleOutline : InformationOutline;
 
-	$: backgroundColor = () => {
-		if (notificationInfo.type === NotificationType.Info) {
-			return '#E0E2F0';
-		}
+  $: backgroundColor = () => {
+    if (notificationInfo.type === NotificationType.Info) {
+      return '#E0E2F0';
+    }
 
-		if (notificationInfo.type === NotificationType.Error) {
-			return '#FBE8E6';
-		}
-	};
+    if (notificationInfo.type === NotificationType.Error) {
+      return '#FBE8E6';
+    }
+  };
 
-	$: borderStyle = () => {
-		if (notificationInfo.type === NotificationType.Info) {
-			return '1px solid #D8DDFF';
-		}
+  $: borderStyle = () => {
+    if (notificationInfo.type === NotificationType.Info) {
+      return '1px solid #D8DDFF';
+    }
 
-		if (notificationInfo.type === NotificationType.Error) {
-			return '1px solid #F0E8E7';
-		}
-	};
+    if (notificationInfo.type === NotificationType.Error) {
+      return '1px solid #F0E8E7';
+    }
+  };
 
-	$: primaryColor = () => {
-		if (notificationInfo.type === NotificationType.Info) {
-			return infoPrimaryColor;
-		}
+  $: primaryColor = () => {
+    if (notificationInfo.type === NotificationType.Info) {
+      return infoPrimaryColor;
+    }
 
-		if (notificationInfo.type === NotificationType.Error) {
-			return errorPrimaryColor;
-		}
-	};
+    if (notificationInfo.type === NotificationType.Error) {
+      return errorPrimaryColor;
+    }
+  };
 
-	let removeNotificationTimeout: NodeJS.Timeout | undefined = undefined;
+  let removeNotificationTimeout: NodeJS.Timeout | undefined = undefined;
 
-	onMount(() => {
-		removeNotificationTimeout = setTimeout(discard, notificationInfo.timeout);
-		return () => clearTimeout(removeNotificationTimeout);
-	});
+  onMount(() => {
+    removeNotificationTimeout = setTimeout(discard, notificationInfo.timeout);
+    return () => clearTimeout(removeNotificationTimeout);
+  });
 
-	const discard = () => {
-		notificationController.removeNotificationById(notificationInfo.id);
-	};
+  const discard = () => {
+    notificationController.removeNotificationById(notificationInfo.id);
+  };
 
-	const handleClick = () => {
-		const action = notificationInfo.action;
-		if (action.type == 'discard') {
-			discard();
-		} else if (action.type == 'link') {
-			window.open(action.target);
-		}
-	};
+  const handleClick = () => {
+    const action = notificationInfo.action;
+    if (action.type == 'discard') {
+      discard();
+    } else if (action.type == 'link') {
+      window.open(action.target);
+    }
+  };
 </script>
 
 <div
-	transition:fade={{ duration: 250 }}
-	style:background-color={backgroundColor()}
-	style:border={borderStyle()}
-	class="min-h-[80px] w-[300px] rounded-2xl z-[999999] shadow-md p-4 mb-4 hover:cursor-pointer"
-	on:click={handleClick}
-	on:keydown={handleClick}
+  transition:fade={{ duration: 250 }}
+  style:background-color={backgroundColor()}
+  style:border={borderStyle()}
+  class="min-h-[80px] w-[300px] rounded-2xl z-[999999] shadow-md p-4 mb-4 hover:cursor-pointer"
+  on:click={handleClick}
+  on:keydown={handleClick}
 >
-	<div class="flex justify-between">
-		<div class="flex gap-2 place-items-center">
-			<svelte:component this={icon} color={primaryColor()} size="20" />
-			<h2 style:color={primaryColor()} class="font-medium" data-testid="title">
-				{notificationInfo.type.toString()}
-			</h2>
-		</div>
-		<button on:click|stopPropagation={discard}>
-			<svelte:component this={WindowClose} size="20" />
-		</button>
-	</div>
+  <div class="flex justify-between">
+    <div class="flex gap-2 place-items-center">
+      <svelte:component this={icon} color={primaryColor()} size="20" />
+      <h2 style:color={primaryColor()} class="font-medium" data-testid="title">
+        {notificationInfo.type.toString()}
+      </h2>
+    </div>
+    <button on:click|stopPropagation={discard}>
+      <svelte:component this={WindowClose} size="20" />
+    </button>
+  </div>
 
-	<p class="whitespace-pre-wrap text-sm pl-[28px] pr-[16px]" data-testid="message">
-		{notificationInfo.message}
-	</p>
+  <p class="whitespace-pre-wrap text-sm pl-[28px] pr-[16px]" data-testid="message">
+    {notificationInfo.message}
+  </p>
 </div>
diff --git a/web/src/lib/components/shared-components/notification/notification-list.svelte b/web/src/lib/components/shared-components/notification/notification-list.svelte
index 205346b61d..af795072a8 100644
--- a/web/src/lib/components/shared-components/notification/notification-list.svelte
+++ b/web/src/lib/components/shared-components/notification/notification-list.svelte
@@ -1,24 +1,20 @@
 <script lang="ts">
-	import { notificationController } from './notification';
-	import { fade } from 'svelte/transition';
+  import { notificationController } from './notification';
+  import { fade } from 'svelte/transition';
 
-	import NotificationCard from './notification-card.svelte';
-	import { flip } from 'svelte/animate';
-	import { quintOut } from 'svelte/easing';
+  import NotificationCard from './notification-card.svelte';
+  import { flip } from 'svelte/animate';
+  import { quintOut } from 'svelte/easing';
 
-	const { notificationList } = notificationController;
+  const { notificationList } = notificationController;
 </script>
 
 {#if $notificationList.length > 0}
-	<section
-		transition:fade={{ duration: 250 }}
-		id="notification-list"
-		class="absolute right-5 top-[80px] z-[99999999]"
-	>
-		{#each $notificationList as notificationInfo (notificationInfo.id)}
-			<div animate:flip={{ duration: 250, easing: quintOut }}>
-				<NotificationCard {notificationInfo} />
-			</div>
-		{/each}
-	</section>
+  <section transition:fade={{ duration: 250 }} id="notification-list" class="absolute right-5 top-[80px] z-[99999999]">
+    {#each $notificationList as notificationInfo (notificationInfo.id)}
+      <div animate:flip={{ duration: 250, easing: quintOut }}>
+        <NotificationCard {notificationInfo} />
+      </div>
+    {/each}
+  </section>
 {/if}
diff --git a/web/src/lib/components/shared-components/notification/notification.ts b/web/src/lib/components/shared-components/notification/notification.ts
index 8dccdd653e..ff1e6d5951 100644
--- a/web/src/lib/components/shared-components/notification/notification.ts
+++ b/web/src/lib/components/shared-components/notification/notification.ts
@@ -1,16 +1,16 @@
 import { writable } from 'svelte/store';
 
 export enum NotificationType {
-	Info = 'Info',
-	Error = 'Error'
+  Info = 'Info',
+  Error = 'Error',
 }
 
 export class ImmichNotification {
-	id = new Date().getTime();
-	type!: NotificationType;
-	message!: string;
-	action!: NotificationAction;
-	timeout = 3000;
+  id = new Date().getTime();
+  type!: NotificationType;
+  message!: string;
+  action!: NotificationAction;
+  timeout = 3000;
 }
 
 type DiscardAction = { type: 'discard' };
@@ -19,50 +19,50 @@ type LinkAction = { type: 'link'; target: string };
 export type NotificationAction = DiscardAction | NoopAction | LinkAction;
 
 export class ImmichNotificationDto {
-	/**
-	 * Notification type
-	 * @type {NotificationType} [Info, Error]
-	 */
-	type: NotificationType = NotificationType.Info;
+  /**
+   * Notification type
+   * @type {NotificationType} [Info, Error]
+   */
+  type: NotificationType = NotificationType.Info;
 
-	/**
-	 * Notification message
-	 */
-	message = '';
+  /**
+   * Notification message
+   */
+  message = '';
 
-	/**
-	 * Timeout in miliseconds
-	 */
-	timeout?: number;
+  /**
+   * Timeout in miliseconds
+   */
+  timeout?: number;
 
-	/**
-	 * The action to take when the notification is clicked
-	 */
-	action?: NotificationAction;
+  /**
+   * The action to take when the notification is clicked
+   */
+  action?: NotificationAction;
 }
 
 function createNotificationList() {
-	const notificationList = writable<ImmichNotification[]>([]);
+  const notificationList = writable<ImmichNotification[]>([]);
 
-	const show = (notificationInfo: ImmichNotificationDto) => {
-		const newNotification = new ImmichNotification();
-		newNotification.message = notificationInfo.message;
-		newNotification.type = notificationInfo.type;
-		newNotification.timeout = notificationInfo.timeout || 3000;
-		newNotification.action = notificationInfo.action || { type: 'discard' };
+  const show = (notificationInfo: ImmichNotificationDto) => {
+    const newNotification = new ImmichNotification();
+    newNotification.message = notificationInfo.message;
+    newNotification.type = notificationInfo.type;
+    newNotification.timeout = notificationInfo.timeout || 3000;
+    newNotification.action = notificationInfo.action || { type: 'discard' };
 
-		notificationList.update((currentList) => [...currentList, newNotification]);
-	};
+    notificationList.update((currentList) => [...currentList, newNotification]);
+  };
 
-	const removeNotificationById = (id: number) => {
-		notificationList.update((currentList) => currentList.filter((n) => n.id != id));
-	};
+  const removeNotificationById = (id: number) => {
+    notificationList.update((currentList) => currentList.filter((n) => n.id != id));
+  };
 
-	return {
-		show,
-		removeNotificationById,
-		notificationList
-	};
+  return {
+    show,
+    removeNotificationById,
+    notificationList,
+  };
 }
 
 export const notificationController = createNotificationList();
diff --git a/web/src/lib/components/shared-components/portal/portal.svelte b/web/src/lib/components/shared-components/portal/portal.svelte
index 4443896d61..431950fd3b 100644
--- a/web/src/lib/components/shared-components/portal/portal.svelte
+++ b/web/src/lib/components/shared-components/portal/portal.svelte
@@ -1,56 +1,56 @@
 <script context="module" lang="ts">
-	import { tick } from 'svelte';
+  import { tick } from 'svelte';
 
-	/**
-	 * Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}>
-	 */
-	export function portal(el: HTMLElement, target: HTMLElement | string = 'body') {
-		let targetEl;
-		async function update(newTarget: HTMLElement | string) {
-			target = newTarget;
-			if (typeof target === 'string') {
-				targetEl = document.querySelector(target);
-				if (targetEl === null) {
-					await tick();
-					targetEl = document.querySelector(target);
-				}
-				if (targetEl === null) {
-					throw new Error(`No element found matching css selector: "${target}"`);
-				}
-			} else if (target instanceof HTMLElement) {
-				targetEl = target;
-			} else {
-				throw new TypeError(
-					`Unknown portal target type: ${
-						target === null ? 'null' : typeof target
-					}. Allowed types: string (CSS selector) or HTMLElement.`
-				);
-			}
-			targetEl.appendChild(el);
-			el.hidden = false;
-		}
+  /**
+   * Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}>
+   */
+  export function portal(el: HTMLElement, target: HTMLElement | string = 'body') {
+    let targetEl;
+    async function update(newTarget: HTMLElement | string) {
+      target = newTarget;
+      if (typeof target === 'string') {
+        targetEl = document.querySelector(target);
+        if (targetEl === null) {
+          await tick();
+          targetEl = document.querySelector(target);
+        }
+        if (targetEl === null) {
+          throw new Error(`No element found matching css selector: "${target}"`);
+        }
+      } else if (target instanceof HTMLElement) {
+        targetEl = target;
+      } else {
+        throw new TypeError(
+          `Unknown portal target type: ${
+            target === null ? 'null' : typeof target
+          }. Allowed types: string (CSS selector) or HTMLElement.`,
+        );
+      }
+      targetEl.appendChild(el);
+      el.hidden = false;
+    }
 
-		function destroy() {
-			if (el.parentNode) {
-				el.parentNode.removeChild(el);
-			}
-		}
+    function destroy() {
+      if (el.parentNode) {
+        el.parentNode.removeChild(el);
+      }
+    }
 
-		update(target);
-		return {
-			update,
-			destroy
-		};
-	}
+    update(target);
+    return {
+      update,
+      destroy,
+    };
+  }
 </script>
 
 <script lang="ts">
-	/**
-	 * DOM Element or CSS Selector
-	 */
-	export let target: HTMLElement | string = 'body';
+  /**
+   * DOM Element or CSS Selector
+   */
+  export let target: HTMLElement | string = 'body';
 </script>
 
 <div use:portal={target} hidden>
-	<slot />
+  <slot />
 </div>
diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte
index 4821f7531f..2f707c9614 100644
--- a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte
+++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte
@@ -1,161 +1,161 @@
 <script lang="ts" context="module">
-	type OnScrollbarClick = {
-		onscrollbarclick: OnScrollbarClickDetail;
-	};
+  type OnScrollbarClick = {
+    onscrollbarclick: OnScrollbarClickDetail;
+  };
 
-	export type OnScrollbarClickDetail = {
-		scrollTo: number;
-	};
+  export type OnScrollbarClickDetail = {
+    scrollTo: number;
+  };
 
-	type OnScrollbarDrag = {
-		onscrollbardrag: OnScrollbarDragDetail;
-	};
+  type OnScrollbarDrag = {
+    onscrollbardrag: OnScrollbarDragDetail;
+  };
 
-	export type OnScrollbarDragDetail = {
-		scrollTo: number;
-	};
+  export type OnScrollbarDragDetail = {
+    scrollTo: number;
+  };
 </script>
 
 <script lang="ts">
-	import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
+  import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
 
-	import { assetGridState } from '$lib/stores/assets.store';
+  import { assetGridState } from '$lib/stores/assets.store';
 
-	import { createEventDispatcher } from 'svelte';
-	import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
+  import { createEventDispatcher } from 'svelte';
+  import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
 
-	export let scrollTop = 0;
-	export let scrollbarHeight = 0;
+  export let scrollTop = 0;
+  export let scrollbarHeight = 0;
 
-	$: timelineHeight = $assetGridState.timelineHeight;
-	$: timelineScrolltop = (scrollbarPosition / scrollbarHeight) * timelineHeight;
+  $: timelineHeight = $assetGridState.timelineHeight;
+  $: timelineScrolltop = (scrollbarPosition / scrollbarHeight) * timelineHeight;
 
-	let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
-	let isHover = false;
-	let isDragging = false;
-	let hoveredDate: Date;
-	let currentMouseYLocation = 0;
-	let scrollbarPosition = 0;
-	let animationTick = false;
+  let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
+  let isHover = false;
+  let isDragging = false;
+  let hoveredDate: Date;
+  let currentMouseYLocation = 0;
+  let scrollbarPosition = 0;
+  let animationTick = false;
 
-	const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
-	$: offset = $isAlbumAssetSelectionOpen ? 100 : 71;
-	const dispatchClick = createEventDispatcher<OnScrollbarClick>();
-	const dispatchDrag = createEventDispatcher<OnScrollbarDrag>();
-	$: {
-		scrollbarPosition = (scrollTop / timelineHeight) * scrollbarHeight;
-	}
+  const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
+  $: offset = $isAlbumAssetSelectionOpen ? 100 : 71;
+  const dispatchClick = createEventDispatcher<OnScrollbarClick>();
+  const dispatchDrag = createEventDispatcher<OnScrollbarDrag>();
+  $: {
+    scrollbarPosition = (scrollTop / timelineHeight) * scrollbarHeight;
+  }
 
-	$: {
-		let result: SegmentScrollbarLayout[] = [];
-		for (const bucket of $assetGridState.buckets) {
-			let segmentLayout = new SegmentScrollbarLayout();
-			segmentLayout.count = bucket.assets.length;
-			segmentLayout.height = (bucket.bucketHeight / timelineHeight) * scrollbarHeight;
-			segmentLayout.timeGroup = bucket.bucketDate;
-			result.push(segmentLayout);
-		}
-		segmentScrollbarLayout = result;
-	}
+  $: {
+    let result: SegmentScrollbarLayout[] = [];
+    for (const bucket of $assetGridState.buckets) {
+      let segmentLayout = new SegmentScrollbarLayout();
+      segmentLayout.count = bucket.assets.length;
+      segmentLayout.height = (bucket.bucketHeight / timelineHeight) * scrollbarHeight;
+      segmentLayout.timeGroup = bucket.bucketDate;
+      result.push(segmentLayout);
+    }
+    segmentScrollbarLayout = result;
+  }
 
-	const handleMouseMove = (e: MouseEvent, currentDate: Date) => {
-		currentMouseYLocation = e.clientY - offset - 30;
+  const handleMouseMove = (e: MouseEvent, currentDate: Date) => {
+    currentMouseYLocation = e.clientY - offset - 30;
 
-		hoveredDate = new Date(currentDate.toISOString().slice(0, -1));
-	};
+    hoveredDate = new Date(currentDate.toISOString().slice(0, -1));
+  };
 
-	const handleMouseDown = (e: MouseEvent) => {
-		isDragging = true;
-		scrollbarPosition = e.clientY - offset;
-	};
+  const handleMouseDown = (e: MouseEvent) => {
+    isDragging = true;
+    scrollbarPosition = e.clientY - offset;
+  };
 
-	const handleMouseUp = (e: MouseEvent) => {
-		isDragging = false;
-		scrollbarPosition = e.clientY - offset;
-		dispatchClick('onscrollbarclick', { scrollTo: timelineScrolltop });
-	};
+  const handleMouseUp = (e: MouseEvent) => {
+    isDragging = false;
+    scrollbarPosition = e.clientY - offset;
+    dispatchClick('onscrollbarclick', { scrollTo: timelineScrolltop });
+  };
 
-	const handleMouseDrag = (e: MouseEvent) => {
-		if (isDragging) {
-			if (!animationTick) {
-				window.requestAnimationFrame(() => {
-					const dy = e.clientY - scrollbarPosition - offset;
-					scrollbarPosition += dy;
-					dispatchDrag('onscrollbardrag', { scrollTo: timelineScrolltop });
-					animationTick = false;
-				});
+  const handleMouseDrag = (e: MouseEvent) => {
+    if (isDragging) {
+      if (!animationTick) {
+        window.requestAnimationFrame(() => {
+          const dy = e.clientY - scrollbarPosition - offset;
+          scrollbarPosition += dy;
+          dispatchDrag('onscrollbardrag', { scrollTo: timelineScrolltop });
+          animationTick = false;
+        });
 
-				animationTick = true;
-			}
-		}
-	};
+        animationTick = true;
+      }
+    }
+  };
 </script>
 
 <div
-	id="immich-scrubbable-scrollbar"
-	class="fixed right-0 bg-immich-bg z-[100] hover:cursor-row-resize select-none"
-	style:width={isDragging ? '100vw' : '60px'}
-	style:background-color={isDragging ? 'transparent' : 'transparent'}
-	on:mouseenter={() => (isHover = true)}
-	on:mouseleave={() => {
-		isHover = false;
-		isDragging = false;
-	}}
-	on:mouseup={handleMouseUp}
-	on:mousemove={handleMouseDrag}
-	on:mousedown={handleMouseDown}
-	style:height={scrollbarHeight + 'px'}
+  id="immich-scrubbable-scrollbar"
+  class="fixed right-0 bg-immich-bg z-[100] hover:cursor-row-resize select-none"
+  style:width={isDragging ? '100vw' : '60px'}
+  style:background-color={isDragging ? 'transparent' : 'transparent'}
+  on:mouseenter={() => (isHover = true)}
+  on:mouseleave={() => {
+    isHover = false;
+    isDragging = false;
+  }}
+  on:mouseup={handleMouseUp}
+  on:mousemove={handleMouseDrag}
+  on:mousedown={handleMouseDown}
+  style:height={scrollbarHeight + 'px'}
 >
-	{#if isHover}
-		<div
-			class="border-b-2 border-immich-primary dark:border-immich-dark-primary w-[100px] right-0 pr-6 py-1 text-sm pl-1 font-medium absolute bg-immich-bg dark:bg-immich-dark-gray z-[100] pointer-events-none rounded-tl-md shadow-lg dark:text-immich-dark-fg"
-			style:top={currentMouseYLocation + 'px'}
-		>
-			{hoveredDate?.toLocaleString('default', { month: 'short' })}
-			{hoveredDate?.getFullYear()}
-		</div>
-	{/if}
+  {#if isHover}
+    <div
+      class="border-b-2 border-immich-primary dark:border-immich-dark-primary w-[100px] right-0 pr-6 py-1 text-sm pl-1 font-medium absolute bg-immich-bg dark:bg-immich-dark-gray z-[100] pointer-events-none rounded-tl-md shadow-lg dark:text-immich-dark-fg"
+      style:top={currentMouseYLocation + 'px'}
+    >
+      {hoveredDate?.toLocaleString('default', { month: 'short' })}
+      {hoveredDate?.getFullYear()}
+    </div>
+  {/if}
 
-	<!-- Scroll Position Indicator Line -->
-	{#if !isDragging}
-		<div
-			class="absolute right-0 w-10 h-[2px] bg-immich-primary dark:bg-immich-dark-primary"
-			style:top={scrollbarPosition + 'px'}
-		/>
-	{/if}
-	<!-- Time Segment -->
-	{#each segmentScrollbarLayout as segment, index (segment.timeGroup)}
-		{@const groupDate = new Date(segment.timeGroup)}
+  <!-- Scroll Position Indicator Line -->
+  {#if !isDragging}
+    <div
+      class="absolute right-0 w-10 h-[2px] bg-immich-primary dark:bg-immich-dark-primary"
+      style:top={scrollbarPosition + 'px'}
+    />
+  {/if}
+  <!-- Time Segment -->
+  {#each segmentScrollbarLayout as segment, index (segment.timeGroup)}
+    {@const groupDate = new Date(segment.timeGroup)}
 
-		<div
-			id="time-segment"
-			class="relative"
-			style:height={segment.height + 'px'}
-			aria-label={segment.timeGroup + ' ' + segment.count}
-			on:mousemove={(e) => handleMouseMove(e, groupDate)}
-		>
-			{#if new Date(segmentScrollbarLayout[index - 1]?.timeGroup).getFullYear() !== groupDate.getFullYear()}
-				{#if segment.height > 8}
-					<div
-						aria-label={segment.timeGroup + ' ' + segment.count}
-						class="absolute right-0 pr-5 z-10 text-xs font-medium dark:text-immich-dark-fg"
-					>
-						{groupDate.getFullYear()}
-					</div>
-				{/if}
-			{:else if segment.height > 5}
-				<div
-					aria-label={segment.timeGroup + ' ' + segment.count}
-					class="absolute right-0 rounded-full h-[4px] w-[4px] mr-3 bg-gray-300 block"
-				/>
-			{/if}
-		</div>
-	{/each}
+    <div
+      id="time-segment"
+      class="relative"
+      style:height={segment.height + 'px'}
+      aria-label={segment.timeGroup + ' ' + segment.count}
+      on:mousemove={(e) => handleMouseMove(e, groupDate)}
+    >
+      {#if new Date(segmentScrollbarLayout[index - 1]?.timeGroup).getFullYear() !== groupDate.getFullYear()}
+        {#if segment.height > 8}
+          <div
+            aria-label={segment.timeGroup + ' ' + segment.count}
+            class="absolute right-0 pr-5 z-10 text-xs font-medium dark:text-immich-dark-fg"
+          >
+            {groupDate.getFullYear()}
+          </div>
+        {/if}
+      {:else if segment.height > 5}
+        <div
+          aria-label={segment.timeGroup + ' ' + segment.count}
+          class="absolute right-0 rounded-full h-[4px] w-[4px] mr-3 bg-gray-300 block"
+        />
+      {/if}
+    </div>
+  {/each}
 </div>
 
 <style>
-	#immich-scrubbable-scrollbar,
-	#time-segment {
-		contain: layout;
-	}
+  #immich-scrubbable-scrollbar,
+  #time-segment {
+    contain: layout;
+  }
 </style>
diff --git a/web/src/lib/components/shared-components/scrollbar/segment-scrollbar-layout.ts b/web/src/lib/components/shared-components/scrollbar/segment-scrollbar-layout.ts
index 337ee47f03..c62a942749 100644
--- a/web/src/lib/components/shared-components/scrollbar/segment-scrollbar-layout.ts
+++ b/web/src/lib/components/shared-components/scrollbar/segment-scrollbar-layout.ts
@@ -1,5 +1,5 @@
 export class SegmentScrollbarLayout {
-	height!: number;
-	timeGroup!: string;
-	count!: number;
+  height!: number;
+  timeGroup!: string;
+  count!: number;
 }
diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte
index 8726da558a..8ae28760c3 100644
--- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte
+++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte
@@ -1,129 +1,129 @@
 <script lang="ts">
-	import { AppRoute } from '$lib/constants';
-	import Magnify from 'svelte-material-icons/Magnify.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import { goto } from '$app/navigation';
-	import { savedSearchTerms } from '$lib/stores/search.store';
-	import { fly } from 'svelte/transition';
-	export let value = '';
-	export let grayTheme: boolean;
+  import { AppRoute } from '$lib/constants';
+  import Magnify from 'svelte-material-icons/Magnify.svelte';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import { goto } from '$app/navigation';
+  import { savedSearchTerms } from '$lib/stores/search.store';
+  import { fly } from 'svelte/transition';
+  export let value = '';
+  export let grayTheme: boolean;
 
-	let showBigSearchBar = false;
-	$: showClearIcon = value.length > 0;
+  let showBigSearchBar = false;
+  $: showClearIcon = value.length > 0;
 
-	function onSearch(saveSearch: boolean) {
-		let clipSearch = 'true';
-		let searchValue = value;
+  function onSearch(saveSearch: boolean) {
+    let clipSearch = 'true';
+    let searchValue = value;
 
-		if (value.slice(0, 2) == 'm:') {
-			clipSearch = 'false';
-			searchValue = value.slice(2);
-		}
+    if (value.slice(0, 2) == 'm:') {
+      clipSearch = 'false';
+      searchValue = value.slice(2);
+    }
 
-		if (saveSearch) {
-			saveSearchTerm(value);
-		}
+    if (saveSearch) {
+      saveSearchTerm(value);
+    }
 
-		const params = new URLSearchParams({
-			q: searchValue,
-			clip: clipSearch
-		});
+    const params = new URLSearchParams({
+      q: searchValue,
+      clip: clipSearch,
+    });
 
-		goto(`${AppRoute.SEARCH}?${params}`);
-	}
+    goto(`${AppRoute.SEARCH}?${params}`);
+  }
 
-	const saveSearchTerm = (saveValue: string) => {
-		$savedSearchTerms = [saveValue, ...$savedSearchTerms];
+  const saveSearchTerm = (saveValue: string) => {
+    $savedSearchTerms = [saveValue, ...$savedSearchTerms];
 
-		if ($savedSearchTerms.length > 5) {
-			$savedSearchTerms = $savedSearchTerms.slice(0, 5);
-		}
-	};
+    if ($savedSearchTerms.length > 5) {
+      $savedSearchTerms = $savedSearchTerms.slice(0, 5);
+    }
+  };
 
-	const clearSearchTerm = () => {
-		$savedSearchTerms = [];
-	};
+  const clearSearchTerm = () => {
+    $savedSearchTerms = [];
+  };
 </script>
 
 <form
-	draggable="false"
-	autocomplete="off"
-	class="relative text-sm"
-	action={AppRoute.SEARCH}
-	on:reset={() => (value = '')}
-	on:submit|preventDefault={() => onSearch(true)}
-	on:focusin={() => (showBigSearchBar = true)}
-	on:focusout={() => (showBigSearchBar = false)}
+  draggable="false"
+  autocomplete="off"
+  class="relative text-sm"
+  action={AppRoute.SEARCH}
+  on:reset={() => (value = '')}
+  on:submit|preventDefault={() => onSearch(true)}
+  on:focusin={() => (showBigSearchBar = true)}
+  on:focusout={() => (showBigSearchBar = false)}
 >
-	<label>
-		<div class="absolute inset-y-0 left-0 flex items-center pl-6">
-			<div class="pointer-events-none dark:text-immich-dark-fg/75">
-				<Magnify size="1.5em" />
-			</div>
-		</div>
-		<input
-			type="text"
-			name="q"
-			class="w-full transition-all {grayTheme
-				? 'dark:bg-immich-dark-gray'
-				: 'dark:bg-immich-dark-bg'} text-immich-fg/75 dark:text-immich-dark-fg px-14 py-4 {showBigSearchBar
-				? 'rounded-t-3xl bg-white  border border-gray-200 dark:border-gray-800'
-				: 'rounded-3xl bg-gray-200 border border-transparent'}"
-			placeholder="Search your photos"
-			required
-			pattern="^(?!m:$).*$"
-			bind:value
-		/>
-	</label>
-	{#if showClearIcon}
-		<div class="absolute inset-y-0 right-0 flex items-center pr-4">
-			<button
-				type="reset"
-				class="dark:text-immich-dark-fg/75 hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 rounded-full p-2 active:bg-immich-primary/10 dark:active:bg-immich-dark-primary/[.35]"
-			>
-				<Close size="1.5em" />
-			</button>
-		</div>
-	{/if}
+  <label>
+    <div class="absolute inset-y-0 left-0 flex items-center pl-6">
+      <div class="pointer-events-none dark:text-immich-dark-fg/75">
+        <Magnify size="1.5em" />
+      </div>
+    </div>
+    <input
+      type="text"
+      name="q"
+      class="w-full transition-all {grayTheme
+        ? 'dark:bg-immich-dark-gray'
+        : 'dark:bg-immich-dark-bg'} text-immich-fg/75 dark:text-immich-dark-fg px-14 py-4 {showBigSearchBar
+        ? 'rounded-t-3xl bg-white  border border-gray-200 dark:border-gray-800'
+        : 'rounded-3xl bg-gray-200 border border-transparent'}"
+      placeholder="Search your photos"
+      required
+      pattern="^(?!m:$).*$"
+      bind:value
+    />
+  </label>
+  {#if showClearIcon}
+    <div class="absolute inset-y-0 right-0 flex items-center pr-4">
+      <button
+        type="reset"
+        class="dark:text-immich-dark-fg/75 hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 rounded-full p-2 active:bg-immich-primary/10 dark:active:bg-immich-dark-primary/[.35]"
+      >
+        <Close size="1.5em" />
+      </button>
+    </div>
+  {/if}
 
-	{#if showBigSearchBar}
-		<div
-			transition:fly={{ y: 25, duration: 250 }}
-			class="w-full pb-5 absolute bg-white transition-all rounded-b-3xl shadow-2xl border border-gray-200 dark:bg-immich-dark-gray dark:border-gray-800 dark:text-gray-300"
-		>
-			<div class="px-5 pt-5 text-xs">
-				<p>
-					Smart search is enabled by default, to search for metadata use the syntax <span
-						class="font-mono p-2 font-semibold text-immich-primary dark:text-immich-dark-primary bg-gray-100 rounded-lg dark:bg-gray-900 leading-7"
-						>m:your-search-term</span
-					>
-				</p>
-			</div>
+  {#if showBigSearchBar}
+    <div
+      transition:fly={{ y: 25, duration: 250 }}
+      class="w-full pb-5 absolute bg-white transition-all rounded-b-3xl shadow-2xl border border-gray-200 dark:bg-immich-dark-gray dark:border-gray-800 dark:text-gray-300"
+    >
+      <div class="px-5 pt-5 text-xs">
+        <p>
+          Smart search is enabled by default, to search for metadata use the syntax <span
+            class="font-mono p-2 font-semibold text-immich-primary dark:text-immich-dark-primary bg-gray-100 rounded-lg dark:bg-gray-900 leading-7"
+            >m:your-search-term</span
+          >
+        </p>
+      </div>
 
-			{#if $savedSearchTerms.length > 0}
-				<div class="px-5 pt-5 text-xs flex justify-between">
-					<p>RECENT SEARCHES</p>
-					<button
-						type="button"
-						class="text-immich-primary dark:text-immich-dark-primary font-semibold p-2 hover:bg-immich-primary/25 rounded-lg"
-						on:click={clearSearchTerm}>Clear all</button
-					>
-				</div>
-			{/if}
+      {#if $savedSearchTerms.length > 0}
+        <div class="px-5 pt-5 text-xs flex justify-between">
+          <p>RECENT SEARCHES</p>
+          <button
+            type="button"
+            class="text-immich-primary dark:text-immich-dark-primary font-semibold p-2 hover:bg-immich-primary/25 rounded-lg"
+            on:click={clearSearchTerm}>Clear all</button
+          >
+        </div>
+      {/if}
 
-			{#each $savedSearchTerms as savedSearchTerm, i (i)}
-				<button
-					type="button"
-					class="w-full hover:bg-gray-100 dark:hover:bg-gray-500/10 px-5 py-3 cursor-pointer flex gap-3 text-black dark:text-gray-300"
-					on:click={() => {
-						value = savedSearchTerm;
-						onSearch(false);
-					}}
-				>
-					<Magnify size="1.5em" />
-					{savedSearchTerm}
-				</button>
-			{/each}
-		</div>
-	{/if}
+      {#each $savedSearchTerms as savedSearchTerm, i (i)}
+        <button
+          type="button"
+          class="w-full hover:bg-gray-100 dark:hover:bg-gray-500/10 px-5 py-3 cursor-pointer flex gap-3 text-black dark:text-gray-300"
+          on:click={() => {
+            value = savedSearchTerm;
+            onSearch(false);
+          }}
+        >
+          <Magnify size="1.5em" />
+          {savedSearchTerm}
+        </button>
+      {/each}
+    </div>
+  {/if}
 </form>
diff --git a/web/src/lib/components/shared-components/side-bar/side-bar-button.svelte b/web/src/lib/components/shared-components/side-bar/side-bar-button.svelte
index 0ac5815667..ec0858a965 100644
--- a/web/src/lib/components/shared-components/side-bar/side-bar-button.svelte
+++ b/web/src/lib/components/shared-components/side-bar/side-bar-button.svelte
@@ -1,64 +1,60 @@
 <script lang="ts">
-	import type Icon from 'svelte-material-icons/AbTesting.svelte';
-	import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
-	import { fade } from 'svelte/transition';
-	import { createEventDispatcher } from 'svelte';
+  import type Icon from 'svelte-material-icons/AbTesting.svelte';
+  import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
+  import { fade } from 'svelte/transition';
+  import { createEventDispatcher } from 'svelte';
 
-	export let title: string;
-	export let logo: typeof Icon;
-	export let isSelected: boolean;
-	export let flippedLogo = false;
+  export let title: string;
+  export let logo: typeof Icon;
+  export let isSelected: boolean;
+  export let flippedLogo = false;
 
-	let showMoreInformation = false;
+  let showMoreInformation = false;
 
-	const dispatch = createEventDispatcher();
-	const onButtonClicked = () => dispatch('selected');
+  const dispatch = createEventDispatcher();
+  const onButtonClicked = () => dispatch('selected');
 </script>
 
 <div
-	on:click={onButtonClicked}
-	on:keydown={onButtonClicked}
-	class="flex gap-4 justify-between place-items-center w-full transition-[padding] delay-100 duration-100 py-3 rounded-r-full hover:bg-immich-gray dark:hover:bg-immich-dark-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary hover:cursor-pointer
+  on:click={onButtonClicked}
+  on:keydown={onButtonClicked}
+  class="flex gap-4 justify-between place-items-center w-full transition-[padding] delay-100 duration-100 py-3 rounded-r-full hover:bg-immich-gray dark:hover:bg-immich-dark-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary hover:cursor-pointer
     {isSelected
-		? 'bg-immich-primary/10 dark:bg-immich-dark-primary/10 text-immich-primary dark:text-immich-dark-primary hover:bg-immich-primary/25'
-		: ''}
+    ? 'bg-immich-primary/10 dark:bg-immich-dark-primary/10 text-immich-primary dark:text-immich-dark-primary hover:bg-immich-primary/25'
+    : ''}
 		pl-5 group-hover:sm:px-5 md:px-5
   "
 >
-	<div class="flex gap-4 place-items-center w-full overflow-hidden truncate">
-		<svelte:component
-			this={logo}
-			size="1.5em"
-			class="shrink-0 {flippedLogo ? '-scale-x-100' : ''}"
-		/>
-		<p class="font-medium text-sm">{title}</p>
-	</div>
+  <div class="flex gap-4 place-items-center w-full overflow-hidden truncate">
+    <svelte:component this={logo} size="1.5em" class="shrink-0 {flippedLogo ? '-scale-x-100' : ''}" />
+    <p class="font-medium text-sm">{title}</p>
+  </div>
 
-	<div
-		class="transition-[height] group-hover:sm:overflow-visible md:overflow-visible overflow-hidden duration-100 delay-1000 sm:group-hover:h-auto md:h-auto h-0"
-	>
-		{#if $$slots.moreInformation}
-			<div
-				class="relative flex justify-center select-none cursor-default"
-				on:mouseenter={() => (showMoreInformation = true)}
-				on:mouseleave={() => (showMoreInformation = false)}
-			>
-				<div class="hover:cursor-help p-1 text-gray-600 dark:text-gray-400">
-					<InformationOutline />
-				</div>
+  <div
+    class="transition-[height] group-hover:sm:overflow-visible md:overflow-visible overflow-hidden duration-100 delay-1000 sm:group-hover:h-auto md:h-auto h-0"
+  >
+    {#if $$slots.moreInformation}
+      <div
+        class="relative flex justify-center select-none cursor-default"
+        on:mouseenter={() => (showMoreInformation = true)}
+        on:mouseleave={() => (showMoreInformation = false)}
+      >
+        <div class="hover:cursor-help p-1 text-gray-600 dark:text-gray-400">
+          <InformationOutline />
+        </div>
 
-				{#if showMoreInformation}
-					<div class="absolute right-6 top-0">
-						<div
-							class="flex place-items-center place-content-center whitespace-nowrap rounded-3xl shadow-lg py-3 px-6 bg-immich-bg text-immich-fg dark:bg-gray-600 dark:text-immich-dark-fg text-xs border dark:border-immich-dark-gray"
-							class:hidden={!showMoreInformation}
-							transition:fade={{ duration: 200 }}
-						>
-							<slot name="moreInformation" />
-						</div>
-					</div>
-				{/if}
-			</div>
-		{/if}
-	</div>
+        {#if showMoreInformation}
+          <div class="absolute right-6 top-0">
+            <div
+              class="flex place-items-center place-content-center whitespace-nowrap rounded-3xl shadow-lg py-3 px-6 bg-immich-bg text-immich-fg dark:bg-gray-600 dark:text-immich-dark-fg text-xs border dark:border-immich-dark-gray"
+              class:hidden={!showMoreInformation}
+              transition:fade={{ duration: 200 }}
+            >
+              <slot name="moreInformation" />
+            </div>
+          </div>
+        {/if}
+      </div>
+    {/if}
+  </div>
 </div>
diff --git a/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte b/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte
index 5ec4b755e5..4e72c46d1a 100644
--- a/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte
+++ b/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte
@@ -2,8 +2,8 @@
 </script>
 
 <section
-	id="sidebar"
-	class="group flex flex-col gap-1 pt-8 bg-immich-bg dark:bg-immich-dark-bg transition-all duration-200 z-10 w-18 md:w-64 hover:sm:pr-6 md:pr-6 hover:sm:w-64 hover:sm:shadow-2xl hover:md:shadow-none hover:md:border-none hover:sm:border-r hover:sm:dark:border-r-immich-dark-gray relative overflow-y-auto immich-scrollbar"
+  id="sidebar"
+  class="group flex flex-col gap-1 pt-8 bg-immich-bg dark:bg-immich-dark-bg transition-all duration-200 z-10 w-18 md:w-64 hover:sm:pr-6 md:pr-6 hover:sm:w-64 hover:sm:shadow-2xl hover:md:shadow-none hover:md:border-none hover:sm:border-r hover:sm:dark:border-r-immich-dark-gray relative overflow-y-auto immich-scrollbar"
 >
-	<slot />
+  <slot />
 </section>
diff --git a/web/src/lib/components/shared-components/side-bar/side-bar.svelte b/web/src/lib/components/shared-components/side-bar/side-bar.svelte
index 12513701f2..3d985661ad 100644
--- a/web/src/lib/components/shared-components/side-bar/side-bar.svelte
+++ b/web/src/lib/components/shared-components/side-bar/side-bar.svelte
@@ -1,197 +1,174 @@
 <script lang="ts">
-	import { page } from '$app/stores';
-	import { api } from '@api';
-	import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
-	import AccountMultiple from 'svelte-material-icons/AccountMultiple.svelte';
-	import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
-	import ImageMultipleOutline from 'svelte-material-icons/ImageMultipleOutline.svelte';
-	import ImageMultiple from 'svelte-material-icons/ImageMultiple.svelte';
-	import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
-	import Magnify from 'svelte-material-icons/Magnify.svelte';
-	import Map from 'svelte-material-icons/Map.svelte';
-	import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte';
-	import HeartMultiple from 'svelte-material-icons/HeartMultiple.svelte';
-	import { AppRoute } from '../../../constants';
-	import LoadingSpinner from '../loading-spinner.svelte';
-	import StatusBox from '../status-box.svelte';
-	import SideBarButton from './side-bar-button.svelte';
-	import { locale } from '$lib/stores/preferences.store';
-	import SideBarSection from './side-bar-section.svelte';
+  import { page } from '$app/stores';
+  import { api } from '@api';
+  import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
+  import AccountMultiple from 'svelte-material-icons/AccountMultiple.svelte';
+  import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
+  import ImageMultipleOutline from 'svelte-material-icons/ImageMultipleOutline.svelte';
+  import ImageMultiple from 'svelte-material-icons/ImageMultiple.svelte';
+  import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
+  import Magnify from 'svelte-material-icons/Magnify.svelte';
+  import Map from 'svelte-material-icons/Map.svelte';
+  import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte';
+  import HeartMultiple from 'svelte-material-icons/HeartMultiple.svelte';
+  import { AppRoute } from '../../../constants';
+  import LoadingSpinner from '../loading-spinner.svelte';
+  import StatusBox from '../status-box.svelte';
+  import SideBarButton from './side-bar-button.svelte';
+  import { locale } from '$lib/stores/preferences.store';
+  import SideBarSection from './side-bar-section.svelte';
 
-	const getAssetCount = async () => {
-		const { data: allAssetCount } = await api.assetApi.getAssetCountByUserId();
-		const { data: archivedCount } = await api.assetApi.getArchivedAssetCountByUserId();
+  const getAssetCount = async () => {
+    const { data: allAssetCount } = await api.assetApi.getAssetCountByUserId();
+    const { data: archivedCount } = await api.assetApi.getArchivedAssetCountByUserId();
 
-		return {
-			videos: allAssetCount.videos - archivedCount.videos,
-			photos: allAssetCount.photos - archivedCount.photos
-		};
-	};
+    return {
+      videos: allAssetCount.videos - archivedCount.videos,
+      photos: allAssetCount.photos - archivedCount.photos,
+    };
+  };
 
-	const getFavoriteCount = async () => {
-		try {
-			const { data: assets } = await api.assetApi.getAllAssets({
-				isFavorite: true,
-				withoutThumbs: true
-			});
+  const getFavoriteCount = async () => {
+    try {
+      const { data: assets } = await api.assetApi.getAllAssets({
+        isFavorite: true,
+        withoutThumbs: true,
+      });
 
-			return {
-				favorites: assets.length
-			};
-		} catch {
-			return {
-				favorites: 0
-			};
-		}
-	};
+      return {
+        favorites: assets.length,
+      };
+    } catch {
+      return {
+        favorites: 0,
+      };
+    }
+  };
 
-	const getAlbumCount = async () => {
-		try {
-			const { data: albumCount } = await api.albumApi.getAlbumCount();
-			return albumCount;
-		} catch {
-			return { owned: 0, shared: 0, notShared: 0 };
-		}
-	};
+  const getAlbumCount = async () => {
+    try {
+      const { data: albumCount } = await api.albumApi.getAlbumCount();
+      return albumCount;
+    } catch {
+      return { owned: 0, shared: 0, notShared: 0 };
+    }
+  };
 
-	const getArchivedAssetsCount = async () => {
-		try {
-			const { data: assetCount } = await api.assetApi.getArchivedAssetCountByUserId();
+  const getArchivedAssetsCount = async () => {
+    try {
+      const { data: assetCount } = await api.assetApi.getArchivedAssetCountByUserId();
 
-			return {
-				videos: assetCount.videos,
-				photos: assetCount.photos
-			};
-		} catch {
-			return {
-				videos: 0,
-				photos: 0
-			};
-		}
-	};
+      return {
+        videos: assetCount.videos,
+        photos: assetCount.photos,
+      };
+    } catch {
+      return {
+        videos: 0,
+        photos: 0,
+      };
+    }
+  };
 
-	const isFavoritesSelected = $page.route.id === '/(user)/favorites';
-	const isPhotosSelected = $page.route.id === '/(user)/photos';
-	const isSharingSelected = $page.route.id === '/(user)/sharing';
+  const isFavoritesSelected = $page.route.id === '/(user)/favorites';
+  const isPhotosSelected = $page.route.id === '/(user)/photos';
+  const isSharingSelected = $page.route.id === '/(user)/sharing';
 </script>
 
 <SideBarSection>
-	<a
-		data-sveltekit-preload-data="hover"
-		data-sveltekit-noscroll
-		href={AppRoute.PHOTOS}
-		draggable="false"
-	>
-		<SideBarButton
-			title="Photos"
-			logo={isPhotosSelected ? ImageMultiple : ImageMultipleOutline}
-			isSelected={isPhotosSelected}
-		>
-			<svelte:fragment slot="moreInformation">
-				{#await getAssetCount()}
-					<LoadingSpinner />
-				{:then data}
-					<div>
-						<p>{data.videos.toLocaleString($locale)} Videos</p>
-						<p>{data.photos.toLocaleString($locale)} Photos</p>
-					</div>
-				{/await}
-			</svelte:fragment>
-		</SideBarButton>
-	</a>
-	<a
-		data-sveltekit-preload-data="hover"
-		data-sveltekit-noscroll
-		href={AppRoute.EXPLORE}
-		draggable="false"
-	>
-		<SideBarButton
-			title="Explore"
-			logo={Magnify}
-			isSelected={$page.route.id === '/(user)/explore'}
-		/>
-	</a>
-	<a data-sveltekit-preload-data="hover" href={AppRoute.MAP} draggable="false">
-		<SideBarButton title="Map" logo={Map} isSelected={$page.route.id === '/(user)/map'} />
-	</a>
-	<a data-sveltekit-preload-data="hover" href={AppRoute.SHARING} draggable="false">
-		<SideBarButton
-			title="Sharing"
-			logo={isSharingSelected ? AccountMultiple : AccountMultipleOutline}
-			isSelected={isSharingSelected}
-		>
-			<svelte:fragment slot="moreInformation">
-				{#await getAlbumCount()}
-					<LoadingSpinner />
-				{:then data}
-					<div>
-						<p>{data.shared.toLocaleString($locale)} Albums</p>
-					</div>
-				{/await}
-			</svelte:fragment>
-		</SideBarButton>
-	</a>
+  <a data-sveltekit-preload-data="hover" data-sveltekit-noscroll href={AppRoute.PHOTOS} draggable="false">
+    <SideBarButton
+      title="Photos"
+      logo={isPhotosSelected ? ImageMultiple : ImageMultipleOutline}
+      isSelected={isPhotosSelected}
+    >
+      <svelte:fragment slot="moreInformation">
+        {#await getAssetCount()}
+          <LoadingSpinner />
+        {:then data}
+          <div>
+            <p>{data.videos.toLocaleString($locale)} Videos</p>
+            <p>{data.photos.toLocaleString($locale)} Photos</p>
+          </div>
+        {/await}
+      </svelte:fragment>
+    </SideBarButton>
+  </a>
+  <a data-sveltekit-preload-data="hover" data-sveltekit-noscroll href={AppRoute.EXPLORE} draggable="false">
+    <SideBarButton title="Explore" logo={Magnify} isSelected={$page.route.id === '/(user)/explore'} />
+  </a>
+  <a data-sveltekit-preload-data="hover" href={AppRoute.MAP} draggable="false">
+    <SideBarButton title="Map" logo={Map} isSelected={$page.route.id === '/(user)/map'} />
+  </a>
+  <a data-sveltekit-preload-data="hover" href={AppRoute.SHARING} draggable="false">
+    <SideBarButton
+      title="Sharing"
+      logo={isSharingSelected ? AccountMultiple : AccountMultipleOutline}
+      isSelected={isSharingSelected}
+    >
+      <svelte:fragment slot="moreInformation">
+        {#await getAlbumCount()}
+          <LoadingSpinner />
+        {:then data}
+          <div>
+            <p>{data.shared.toLocaleString($locale)} Albums</p>
+          </div>
+        {/await}
+      </svelte:fragment>
+    </SideBarButton>
+  </a>
 
-	<div class="text-xs dark:text-immich-dark-fg transition-all duration-200">
-		<p class="p-6 hidden md:block group-hover:sm:block">LIBRARY</p>
-		<hr class="mt-8 mb-[31px] mx-4 block md:hidden group-hover:sm:hidden" />
-	</div>
-	<a data-sveltekit-preload-data="hover" href={AppRoute.FAVORITES} draggable="false">
-		<SideBarButton
-			title="Favorites"
-			logo={isFavoritesSelected ? HeartMultiple : HeartMultipleOutline}
-			isSelected={isFavoritesSelected}
-		>
-			<svelte:fragment slot="moreInformation">
-				{#await getFavoriteCount()}
-					<LoadingSpinner />
-				{:then data}
-					<div>
-						<p>{data.favorites} Favorites</p>
-					</div>
-				{/await}
-			</svelte:fragment>
-		</SideBarButton>
-	</a>
-	<a data-sveltekit-preload-data="hover" href={AppRoute.ALBUMS} draggable="false">
-		<SideBarButton
-			title="Albums"
-			logo={ImageAlbum}
-			flippedLogo={true}
-			isSelected={$page.route.id === '/(user)/albums'}
-		>
-			<svelte:fragment slot="moreInformation">
-				{#await getAlbumCount()}
-					<LoadingSpinner />
-				{:then data}
-					<div>
-						<p>{data.owned.toLocaleString($locale)} Albums</p>
-					</div>
-				{/await}
-			</svelte:fragment>
-		</SideBarButton>
-	</a>
-	<a data-sveltekit-preload-data="hover" href={AppRoute.ARCHIVE} draggable="false">
-		<SideBarButton
-			title="Archive"
-			logo={ArchiveArrowDownOutline}
-			isSelected={$page.route.id === '/(user)/archive'}
-		>
-			<svelte:fragment slot="moreInformation">
-				{#await getArchivedAssetsCount()}
-					<LoadingSpinner />
-				{:then data}
-					<div>
-						<p>{data.videos.toLocaleString($locale)} Videos</p>
-						<p>{data.photos.toLocaleString($locale)} Photos</p>
-					</div>
-				{/await}
-			</svelte:fragment>
-		</SideBarButton>
-	</a>
+  <div class="text-xs dark:text-immich-dark-fg transition-all duration-200">
+    <p class="p-6 hidden md:block group-hover:sm:block">LIBRARY</p>
+    <hr class="mt-8 mb-[31px] mx-4 block md:hidden group-hover:sm:hidden" />
+  </div>
+  <a data-sveltekit-preload-data="hover" href={AppRoute.FAVORITES} draggable="false">
+    <SideBarButton
+      title="Favorites"
+      logo={isFavoritesSelected ? HeartMultiple : HeartMultipleOutline}
+      isSelected={isFavoritesSelected}
+    >
+      <svelte:fragment slot="moreInformation">
+        {#await getFavoriteCount()}
+          <LoadingSpinner />
+        {:then data}
+          <div>
+            <p>{data.favorites} Favorites</p>
+          </div>
+        {/await}
+      </svelte:fragment>
+    </SideBarButton>
+  </a>
+  <a data-sveltekit-preload-data="hover" href={AppRoute.ALBUMS} draggable="false">
+    <SideBarButton title="Albums" logo={ImageAlbum} flippedLogo={true} isSelected={$page.route.id === '/(user)/albums'}>
+      <svelte:fragment slot="moreInformation">
+        {#await getAlbumCount()}
+          <LoadingSpinner />
+        {:then data}
+          <div>
+            <p>{data.owned.toLocaleString($locale)} Albums</p>
+          </div>
+        {/await}
+      </svelte:fragment>
+    </SideBarButton>
+  </a>
+  <a data-sveltekit-preload-data="hover" href={AppRoute.ARCHIVE} draggable="false">
+    <SideBarButton title="Archive" logo={ArchiveArrowDownOutline} isSelected={$page.route.id === '/(user)/archive'}>
+      <svelte:fragment slot="moreInformation">
+        {#await getArchivedAssetsCount()}
+          <LoadingSpinner />
+        {:then data}
+          <div>
+            <p>{data.videos.toLocaleString($locale)} Videos</p>
+            <p>{data.photos.toLocaleString($locale)} Photos</p>
+          </div>
+        {/await}
+      </svelte:fragment>
+    </SideBarButton>
+  </a>
 
-	<!-- Status Box -->
-	<div class="mb-6 mt-auto">
-		<StatusBox />
-	</div>
+  <!-- Status Box -->
+  <div class="mb-6 mt-auto">
+    <StatusBox />
+  </div>
 </SideBarSection>
diff --git a/web/src/lib/components/shared-components/status-box.svelte b/web/src/lib/components/shared-components/status-box.svelte
index 19751d52a2..03806f8c70 100644
--- a/web/src/lib/components/shared-components/status-box.svelte
+++ b/web/src/lib/components/shared-components/status-box.svelte
@@ -1,113 +1,109 @@
 <script lang="ts">
-	import { onDestroy, onMount } from 'svelte';
-	import Cloud from 'svelte-material-icons/Cloud.svelte';
-	import Dns from 'svelte-material-icons/Dns.svelte';
-	import LoadingSpinner from './loading-spinner.svelte';
-	import { api, ServerInfoResponseDto } from '@api';
-	import { asByteUnitString } from '../../utils/byte-units';
-	import { locale } from '$lib/stores/preferences.store';
+  import { onDestroy, onMount } from 'svelte';
+  import Cloud from 'svelte-material-icons/Cloud.svelte';
+  import Dns from 'svelte-material-icons/Dns.svelte';
+  import LoadingSpinner from './loading-spinner.svelte';
+  import { api, ServerInfoResponseDto } from '@api';
+  import { asByteUnitString } from '../../utils/byte-units';
+  import { locale } from '$lib/stores/preferences.store';
 
-	let isServerOk = true;
-	let serverVersion = '';
-	let serverInfo: ServerInfoResponseDto;
-	let pingServerInterval: NodeJS.Timer;
+  let isServerOk = true;
+  let serverVersion = '';
+  let serverInfo: ServerInfoResponseDto;
+  let pingServerInterval: NodeJS.Timer;
 
-	onMount(async () => {
-		try {
-			const { data: version } = await api.serverInfoApi.getServerVersion();
+  onMount(async () => {
+    try {
+      const { data: version } = await api.serverInfoApi.getServerVersion();
 
-			serverVersion = `v${version.major}.${version.minor}.${version.patch}`;
+      serverVersion = `v${version.major}.${version.minor}.${version.patch}`;
 
-			const { data: serverInfoRes } = await api.serverInfoApi.getServerInfo();
-			serverInfo = serverInfoRes;
-			getStorageUsagePercentage();
-		} catch (e) {
-			console.log('Error [StatusBox] [onMount]');
-			isServerOk = false;
-		}
+      const { data: serverInfoRes } = await api.serverInfoApi.getServerInfo();
+      serverInfo = serverInfoRes;
+      getStorageUsagePercentage();
+    } catch (e) {
+      console.log('Error [StatusBox] [onMount]');
+      isServerOk = false;
+    }
 
-		pingServerInterval = setInterval(async () => {
-			try {
-				const { data: pingReponse } = await api.serverInfoApi.pingServer();
+    pingServerInterval = setInterval(async () => {
+      try {
+        const { data: pingReponse } = await api.serverInfoApi.pingServer();
 
-				if (pingReponse.res === 'pong') isServerOk = true;
-				else isServerOk = false;
+        if (pingReponse.res === 'pong') isServerOk = true;
+        else isServerOk = false;
 
-				const { data: serverInfoRes } = await api.serverInfoApi.getServerInfo();
-				serverInfo = serverInfoRes;
-			} catch (e) {
-				console.log('Error [StatusBox] [pingServerInterval]', e);
-				isServerOk = false;
-			}
-		}, 10000);
-	});
+        const { data: serverInfoRes } = await api.serverInfoApi.getServerInfo();
+        serverInfo = serverInfoRes;
+      } catch (e) {
+        console.log('Error [StatusBox] [pingServerInterval]', e);
+        isServerOk = false;
+      }
+    }, 10000);
+  });
 
-	onDestroy(() => clearInterval(pingServerInterval));
+  onDestroy(() => clearInterval(pingServerInterval));
 
-	const getStorageUsagePercentage = () => {
-		return Math.round((serverInfo?.diskUseRaw / serverInfo?.diskSizeRaw) * 100);
-	};
+  const getStorageUsagePercentage = () => {
+    return Math.round((serverInfo?.diskUseRaw / serverInfo?.diskSizeRaw) * 100);
+  };
 </script>
 
 <div class="dark:text-immich-dark-fg">
-	<div class="storage-status grid grid-cols-[64px_auto]">
-		<div
-			class="pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary pb-[2.15rem] group-hover:sm:pb-0 md:pb-0"
-		>
-			<Cloud size={'24'} />
-		</div>
-		<div class="hidden md:block group-hover:sm:block">
-			<p class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary">Storage</p>
-			{#if serverInfo}
-				<div class="w-full bg-gray-200 rounded-full h-[7px] dark:bg-gray-700 my-2">
-					<!-- style={`width: ${$downloadAssets[fileName]}%`} -->
-					<div
-						class="bg-immich-primary dark:bg-immich-dark-primary h-[7px] rounded-full"
-						style="width: {getStorageUsagePercentage()}%"
-					/>
-				</div>
-				<p class="text-xs">
-					{asByteUnitString(serverInfo?.diskUseRaw, $locale)} of
-					{asByteUnitString(serverInfo?.diskSizeRaw, $locale)} used
-				</p>
-			{:else}
-				<div class="mt-2">
-					<LoadingSpinner />
-				</div>
-			{/if}
-		</div>
-	</div>
-	<div>
-		<hr class="ml-5 my-4 dark:border-immich-dark-gray" />
-	</div>
-	<div class="server-status grid grid-cols-[64px_auto]">
-		<div
-			class="pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary pb-11 md:pb-0 group-hover:sm:pb-0"
-		>
-			<Dns size={'24'} />
-		</div>
-		<div class="text-xs hidden md:block group-hover:sm:block">
-			<p class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary">Server</p>
+  <div class="storage-status grid grid-cols-[64px_auto]">
+    <div class="pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary pb-[2.15rem] group-hover:sm:pb-0 md:pb-0">
+      <Cloud size={'24'} />
+    </div>
+    <div class="hidden md:block group-hover:sm:block">
+      <p class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary">Storage</p>
+      {#if serverInfo}
+        <div class="w-full bg-gray-200 rounded-full h-[7px] dark:bg-gray-700 my-2">
+          <!-- style={`width: ${$downloadAssets[fileName]}%`} -->
+          <div
+            class="bg-immich-primary dark:bg-immich-dark-primary h-[7px] rounded-full"
+            style="width: {getStorageUsagePercentage()}%"
+          />
+        </div>
+        <p class="text-xs">
+          {asByteUnitString(serverInfo?.diskUseRaw, $locale)} of
+          {asByteUnitString(serverInfo?.diskSizeRaw, $locale)} used
+        </p>
+      {:else}
+        <div class="mt-2">
+          <LoadingSpinner />
+        </div>
+      {/if}
+    </div>
+  </div>
+  <div>
+    <hr class="ml-5 my-4 dark:border-immich-dark-gray" />
+  </div>
+  <div class="server-status grid grid-cols-[64px_auto]">
+    <div class="pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary pb-11 md:pb-0 group-hover:sm:pb-0">
+      <Dns size={'24'} />
+    </div>
+    <div class="text-xs hidden md:block group-hover:sm:block">
+      <p class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary">Server</p>
 
-			<div class="flex justify-items-center justify-between mt-2">
-				<p>Status</p>
+      <div class="flex justify-items-center justify-between mt-2">
+        <p>Status</p>
 
-				{#if isServerOk}
-					<p class="font-medium text-immich-primary dark:text-immich-dark-primary">Online</p>
-				{:else}
-					<p class="font-medium text-red-500">Offline</p>
-				{/if}
-			</div>
+        {#if isServerOk}
+          <p class="font-medium text-immich-primary dark:text-immich-dark-primary">Online</p>
+        {:else}
+          <p class="font-medium text-red-500">Offline</p>
+        {/if}
+      </div>
 
-			<div class="flex justify-items-center justify-between mt-2">
-				<p>Version</p>
-				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-					{serverVersion}
-				</p>
-			</div>
-		</div>
-	</div>
-	<!-- <div>
+      <div class="flex justify-items-center justify-between mt-2">
+        <p>Version</p>
+        <p class="font-medium text-immich-primary dark:text-immich-dark-primary">
+          {serverVersion}
+        </p>
+      </div>
+    </div>
+  </div>
+  <!-- <div>
 		<hr class="ml-5 my-4" />
 	</div>
 	<button class="text-xs ml-5 underline hover:cursor-pointer text-immich-primary" on:click={() => goto('/changelog')}
diff --git a/web/src/lib/components/shared-components/theme-button.svelte b/web/src/lib/components/shared-components/theme-button.svelte
index 19e7cfd4b1..31b423c7bc 100644
--- a/web/src/lib/components/shared-components/theme-button.svelte
+++ b/web/src/lib/components/shared-components/theme-button.svelte
@@ -1,35 +1,35 @@
 <script lang="ts">
-	import { browser } from '$app/environment';
-	import { colorTheme } from '$lib/stores/preferences.store';
-	import IconButton from '../elements/buttons/icon-button.svelte';
+  import { browser } from '$app/environment';
+  import { colorTheme } from '$lib/stores/preferences.store';
+  import IconButton from '../elements/buttons/icon-button.svelte';
 
-	const toggleTheme = () => {
-		$colorTheme = $colorTheme === 'dark' ? 'light' : 'dark';
-	};
+  const toggleTheme = () => {
+    $colorTheme = $colorTheme === 'dark' ? 'light' : 'dark';
+  };
 
-	$: {
-		if (browser) {
-			if ($colorTheme === 'light') {
-				document.documentElement.classList.remove('dark');
-			} else {
-				document.documentElement.classList.add('dark');
-			}
-		}
-	}
+  $: {
+    if (browser) {
+      if ($colorTheme === 'light') {
+        document.documentElement.classList.remove('dark');
+      } else {
+        document.documentElement.classList.add('dark');
+      }
+    }
+  }
 </script>
 
 <IconButton on:click={toggleTheme} title="Toggle theme">
-	{#if $colorTheme === 'light'}
-		<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
-			><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" /></svg
-		>
-	{:else}
-		<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
-			><path
-				d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
-				fill-rule="evenodd"
-				clip-rule="evenodd"
-			/></svg
-		>
-	{/if}
+  {#if $colorTheme === 'light'}
+    <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
+      ><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" /></svg
+    >
+  {:else}
+    <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
+      ><path
+        d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
+        fill-rule="evenodd"
+        clip-rule="evenodd"
+      /></svg
+    >
+  {/if}
 </IconButton>
diff --git a/web/src/lib/components/shared-components/upload-asset-preview.svelte b/web/src/lib/components/shared-components/upload-asset-preview.svelte
index b417b85d67..5af410625e 100644
--- a/web/src/lib/components/shared-components/upload-asset-preview.svelte
+++ b/web/src/lib/components/shared-components/upload-asset-preview.svelte
@@ -1,67 +1,64 @@
 <script lang="ts">
-	import type { UploadAsset } from '$lib/models/upload-asset';
-	import { locale } from '$lib/stores/preferences.store';
-	import { asByteUnitString } from '$lib/utils/byte-units';
-	import { fade } from 'svelte/transition';
-	import ImmichLogo from './immich-logo.svelte';
+  import type { UploadAsset } from '$lib/models/upload-asset';
+  import { locale } from '$lib/stores/preferences.store';
+  import { asByteUnitString } from '$lib/utils/byte-units';
+  import { fade } from 'svelte/transition';
+  import ImmichLogo from './immich-logo.svelte';
 
-	export let uploadAsset: UploadAsset;
+  export let uploadAsset: UploadAsset;
 
-	let showFallbackImage = false;
-	const previewURL = URL.createObjectURL(uploadAsset.file);
+  let showFallbackImage = false;
+  const previewURL = URL.createObjectURL(uploadAsset.file);
 </script>
 
 <div
-	in:fade={{ duration: 250 }}
-	out:fade={{ duration: 100 }}
-	class="text-xs mt-3 rounded-lg bg-immich-bg grid grid-cols-[70px_auto] gap-2 h-[70px]"
+  in:fade={{ duration: 250 }}
+  out:fade={{ duration: 100 }}
+  class="text-xs mt-3 rounded-lg bg-immich-bg grid grid-cols-[70px_auto] gap-2 h-[70px]"
 >
-	<div class="relative">
-		{#if showFallbackImage}
-			<div in:fade={{ duration: 250 }}>
-				<ImmichLogo class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg" />
-			</div>
-		{:else}
-			<img
-				in:fade={{ duration: 250 }}
-				on:load={() => {
-					URL.revokeObjectURL(previewURL);
-				}}
-				on:error={() => {
-					URL.revokeObjectURL(previewURL);
-					showFallbackImage = true;
-				}}
-				src={previewURL}
-				alt="Preview of asset"
-				class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg"
-				draggable="false"
-			/>
-		{/if}
+  <div class="relative">
+    {#if showFallbackImage}
+      <div in:fade={{ duration: 250 }}>
+        <ImmichLogo class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg" />
+      </div>
+    {:else}
+      <img
+        in:fade={{ duration: 250 }}
+        on:load={() => {
+          URL.revokeObjectURL(previewURL);
+        }}
+        on:error={() => {
+          URL.revokeObjectURL(previewURL);
+          showFallbackImage = true;
+        }}
+        src={previewURL}
+        alt="Preview of asset"
+        class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg"
+        draggable="false"
+      />
+    {/if}
 
-		<div class="bottom-0 left-0 absolute w-full h-[25px] bg-immich-primary/30">
-			<p
-				class="absolute bottom-1 right-1 object-right-bottom text-gray-50/95 font-semibold stroke-immich-primary uppercase"
-			>
-				.{uploadAsset.fileExtension}
-			</p>
-		</div>
-	</div>
+    <div class="bottom-0 left-0 absolute w-full h-[25px] bg-immich-primary/30">
+      <p
+        class="absolute bottom-1 right-1 object-right-bottom text-gray-50/95 font-semibold stroke-immich-primary uppercase"
+      >
+        .{uploadAsset.fileExtension}
+      </p>
+    </div>
+  </div>
 
-	<div class="p-2 pr-4 flex flex-col justify-between">
-		<input
-			disabled
-			class="bg-gray-100 border w-full p-1 rounded-md text-[10px] px-2"
-			value={`[${asByteUnitString(uploadAsset.file.size, $locale)}] ${uploadAsset.file.name}`}
-		/>
+  <div class="p-2 pr-4 flex flex-col justify-between">
+    <input
+      disabled
+      class="bg-gray-100 border w-full p-1 rounded-md text-[10px] px-2"
+      value={`[${asByteUnitString(uploadAsset.file.size, $locale)}] ${uploadAsset.file.name}`}
+    />
 
-		<div class="w-full bg-gray-300 h-[15px] rounded-md mt-[5px] text-white relative">
-			<div
-				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>
-		</div>
-	</div>
+    <div class="w-full bg-gray-300 h-[15px] rounded-md mt-[5px] text-white relative">
+      <div 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>
+    </div>
+  </div>
 </div>
diff --git a/web/src/lib/components/shared-components/upload-panel.svelte b/web/src/lib/components/shared-components/upload-panel.svelte
index 5783dccf5e..ecb92bc598 100644
--- a/web/src/lib/components/shared-components/upload-panel.svelte
+++ b/web/src/lib/components/shared-components/upload-panel.svelte
@@ -1,82 +1,82 @@
 <script lang="ts">
-	import { quartInOut } from 'svelte/easing';
-	import { scale, fade } from 'svelte/transition';
-	import { uploadAssetsStore } from '$lib/stores/upload';
-	import CloudUploadOutline from 'svelte-material-icons/CloudUploadOutline.svelte';
-	import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte';
-	import { notificationController, NotificationType } from './notification/notification';
-	import UploadAssetPreview from './upload-asset-preview.svelte';
+  import { quartInOut } from 'svelte/easing';
+  import { scale, fade } from 'svelte/transition';
+  import { uploadAssetsStore } from '$lib/stores/upload';
+  import CloudUploadOutline from 'svelte-material-icons/CloudUploadOutline.svelte';
+  import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte';
+  import { notificationController, NotificationType } from './notification/notification';
+  import UploadAssetPreview from './upload-asset-preview.svelte';
 
-	let showDetail = true;
-	let uploadLength = 0;
-	let isUploading = false;
+  let showDetail = true;
+  let uploadLength = 0;
+  let isUploading = false;
 
-	// Reactive action to update asset uploadLength whenever there is a new one added to the list
-	$: {
-		if ($uploadAssetsStore.length != uploadLength) {
-			uploadLength = $uploadAssetsStore.length;
-		}
-	}
+  // Reactive action to update asset uploadLength whenever there is a new one added to the list
+  $: {
+    if ($uploadAssetsStore.length != uploadLength) {
+      uploadLength = $uploadAssetsStore.length;
+    }
+  }
 
-	uploadAssetsStore.isUploading.subscribe((value) => {
-		isUploading = value;
-	});
+  uploadAssetsStore.isUploading.subscribe((value) => {
+    isUploading = value;
+  });
 </script>
 
 {#if isUploading}
-	<div
-		in:fade={{ duration: 250 }}
-		out:fade={{ duration: 250, delay: 1000 }}
-		on:outroend={() => {
-			notificationController.show({
-				message: 'Upload success, refresh the page to see new upload assets',
-				type: NotificationType.Info
-			});
-		}}
-		class="absolute right-6 bottom-6 z-[10000]"
-	>
-		{#if showDetail}
-			<div
-				in:scale={{ duration: 250, easing: quartInOut }}
-				class="bg-gray-200 p-4 text-sm w-[300px] rounded-lg shadow-sm border"
-			>
-				<div class="flex justify-between place-item-center mb-4">
-					<p class="text-xs text-gray-500">UPLOADING {$uploadAssetsStore.length}</p>
-					<button
-						on:click={() => (showDetail = false)}
-						class="w-[20px] h-[20px] bg-gray-50 rounded-full flex place-items-center place-content-center transition-colors hover:bg-gray-100"
-					>
-						<WindowMinimize />
-					</button>
-				</div>
+  <div
+    in:fade={{ duration: 250 }}
+    out:fade={{ duration: 250, delay: 1000 }}
+    on:outroend={() => {
+      notificationController.show({
+        message: 'Upload success, refresh the page to see new upload assets',
+        type: NotificationType.Info,
+      });
+    }}
+    class="absolute right-6 bottom-6 z-[10000]"
+  >
+    {#if showDetail}
+      <div
+        in:scale={{ duration: 250, easing: quartInOut }}
+        class="bg-gray-200 p-4 text-sm w-[300px] rounded-lg shadow-sm border"
+      >
+        <div class="flex justify-between place-item-center mb-4">
+          <p class="text-xs text-gray-500">UPLOADING {$uploadAssetsStore.length}</p>
+          <button
+            on:click={() => (showDetail = false)}
+            class="w-[20px] h-[20px] bg-gray-50 rounded-full flex place-items-center place-content-center transition-colors hover:bg-gray-100"
+          >
+            <WindowMinimize />
+          </button>
+        </div>
 
-				<div class="max-h-[400px] overflow-y-auto pr-2 rounded-lg immich-scrollbar">
-					{#each $uploadAssetsStore as uploadAsset}
-						{#key uploadAsset.id}
-							<UploadAssetPreview {uploadAsset} />
-						{/key}
-					{/each}
-				</div>
-			</div>
-		{:else}
-			<div class="rounded-full">
-				<button
-					in:scale={{ duration: 250, easing: quartInOut }}
-					on:click={() => (showDetail = true)}
-					class="absolute -top-4 -left-4 text-xs rounded-full w-10 h-10 p-5 flex place-items-center place-content-center bg-immich-primary text-gray-200"
-				>
-					{$uploadAssetsStore.length}
-				</button>
-				<button
-					in:scale={{ duration: 250, easing: quartInOut }}
-					on:click={() => (showDetail = true)}
-					class="bg-gray-300 p-5 rounded-full w-16 h-16 flex place-items-center place-content-center text-sm shadow-lg"
-				>
-					<div class="animate-pulse">
-						<CloudUploadOutline size="30" color="#4250af" />
-					</div>
-				</button>
-			</div>
-		{/if}
-	</div>
+        <div class="max-h-[400px] overflow-y-auto pr-2 rounded-lg immich-scrollbar">
+          {#each $uploadAssetsStore as uploadAsset}
+            {#key uploadAsset.id}
+              <UploadAssetPreview {uploadAsset} />
+            {/key}
+          {/each}
+        </div>
+      </div>
+    {:else}
+      <div class="rounded-full">
+        <button
+          in:scale={{ duration: 250, easing: quartInOut }}
+          on:click={() => (showDetail = true)}
+          class="absolute -top-4 -left-4 text-xs rounded-full w-10 h-10 p-5 flex place-items-center place-content-center bg-immich-primary text-gray-200"
+        >
+          {$uploadAssetsStore.length}
+        </button>
+        <button
+          in:scale={{ duration: 250, easing: quartInOut }}
+          on:click={() => (showDetail = true)}
+          class="bg-gray-300 p-5 rounded-full w-16 h-16 flex place-items-center place-content-center text-sm shadow-lg"
+        >
+          <div class="animate-pulse">
+            <CloudUploadOutline size="30" color="#4250af" />
+          </div>
+        </button>
+      </div>
+    {/if}
+  </div>
 {/if}
diff --git a/web/src/lib/components/shared-components/user-avatar.svelte b/web/src/lib/components/shared-components/user-avatar.svelte
index bf6ff13824..a9ab500a96 100644
--- a/web/src/lib/components/shared-components/user-avatar.svelte
+++ b/web/src/lib/components/shared-components/user-avatar.svelte
@@ -1,79 +1,78 @@
 <script lang="ts" context="module">
-	export type Color = 'primary' | 'pink' | 'red' | 'yellow' | 'blue' | 'green';
-	export type Size = 'full' | 'sm' | 'md' | 'lg';
+  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';
+  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;
+  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 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'
-	};
+  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];
-	}
+  // 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'
-		: '';
+  $: 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}
+  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}
+  {#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>
diff --git a/web/src/lib/components/shared-components/version-announcement-box.svelte b/web/src/lib/components/shared-components/version-announcement-box.svelte
index 944a874e5e..097148f7ca 100644
--- a/web/src/lib/components/shared-components/version-announcement-box.svelte
+++ b/web/src/lib/components/shared-components/version-announcement-box.svelte
@@ -1,80 +1,76 @@
 <script lang="ts">
-	import { getGithubVersion } from '$lib/utils/get-github-version';
-	import { onMount } from 'svelte';
-	import FullScreenModal from './full-screen-modal.svelte';
-	import type { ServerVersionReponseDto } from '@api';
-	import Button from '../elements/buttons/button.svelte';
+  import { getGithubVersion } from '$lib/utils/get-github-version';
+  import { onMount } from 'svelte';
+  import FullScreenModal from './full-screen-modal.svelte';
+  import type { ServerVersionReponseDto } from '@api';
+  import Button from '../elements/buttons/button.svelte';
 
-	export let serverVersion: ServerVersionReponseDto;
+  export let serverVersion: ServerVersionReponseDto;
 
-	let showModal = false;
-	let githubVersion: string;
-	$: serverVersionName = semverToName(serverVersion);
+  let showModal = false;
+  let githubVersion: string;
+  $: serverVersionName = semverToName(serverVersion);
 
-	function semverToName({ major, minor, patch }: ServerVersionReponseDto) {
-		return `v${major}.${minor}.${patch}`;
-	}
+  function semverToName({ major, minor, patch }: ServerVersionReponseDto) {
+    return `v${major}.${minor}.${patch}`;
+  }
 
-	function onAcknowledge() {
-		// Store server version to prevent the notification
-		// from showing again.
-		localStorage.setItem('appVersion', githubVersion);
-		showModal = false;
-	}
+  function onAcknowledge() {
+    // Store server version to prevent the notification
+    // from showing again.
+    localStorage.setItem('appVersion', githubVersion);
+    showModal = false;
+  }
 
-	onMount(async () => {
-		try {
-			githubVersion = await getGithubVersion();
-			if (localStorage.getItem('appVersion') === githubVersion) {
-				// Updated version has already been acknowledged.
-				return;
-			}
+  onMount(async () => {
+    try {
+      githubVersion = await getGithubVersion();
+      if (localStorage.getItem('appVersion') === githubVersion) {
+        // Updated version has already been acknowledged.
+        return;
+      }
 
-			if (githubVersion !== serverVersionName) {
-				showModal = true;
-			}
-		} catch (err) {
-			// Only log any errors that occur.
-			console.error('Error [VersionAnnouncementBox]:', err);
-		}
-	});
+      if (githubVersion !== serverVersionName) {
+        showModal = true;
+      }
+    } catch (err) {
+      // Only log any errors that occur.
+      console.error('Error [VersionAnnouncementBox]:', err);
+    }
+  });
 </script>
 
 {#if showModal}
-	<FullScreenModal on:clickOutside={() => (showModal = false)}>
-		<div
-			class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray shadow-sm max-w-lg rounded-3xl py-10 px-8 dark:text-immich-dark-fg"
-		>
-			<p class="text-2xl mb-4">🎉 NEW VERSION AVAILABLE 🎉</p>
+  <FullScreenModal on:clickOutside={() => (showModal = false)}>
+    <div
+      class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray shadow-sm max-w-lg rounded-3xl py-10 px-8 dark:text-immich-dark-fg"
+    >
+      <p class="text-2xl mb-4">🎉 NEW VERSION AVAILABLE 🎉</p>
 
-			<div>
-				Hi friend, there is a new release of
-				<span class="font-immich-title text-immich-primary dark:text-immich-dark-primary font-bold"
-					>IMMICH</span
-				>, please take your time to visit the
-				<span class="underline font-medium"
-					><a
-						href="https://github.com/immich-app/immich/releases/latest"
-						target="_blank"
-						rel="noopener noreferrer">release notes</a
-					></span
-				>
-				and ensure your <code>docker-compose</code>, and <code>.env</code> setup is up-to-date to prevent
-				any misconfigurations, especially if you use WatchTower or any mechanism that handles updating
-				your application automatically.
-			</div>
+      <div>
+        Hi friend, there is a new release of
+        <span class="font-immich-title text-immich-primary dark:text-immich-dark-primary font-bold">IMMICH</span>,
+        please take your time to visit the
+        <span class="underline font-medium"
+          ><a href="https://github.com/immich-app/immich/releases/latest" target="_blank" rel="noopener noreferrer"
+            >release notes</a
+          ></span
+        >
+        and ensure your <code>docker-compose</code>, and <code>.env</code> setup is up-to-date to prevent any misconfigurations,
+        especially if you use WatchTower or any mechanism that handles updating your application automatically.
+      </div>
 
-			<div class="font-medium mt-4">Your friend, Alex</div>
+      <div class="font-medium mt-4">Your friend, Alex</div>
 
-			<div class="font-sm mt-8">
-				<code>Server Version: {serverVersionName}</code>
-				<br />
-				<code>Latest Version: {githubVersion}</code>
-			</div>
+      <div class="font-sm mt-8">
+        <code>Server Version: {serverVersionName}</code>
+        <br />
+        <code>Latest Version: {githubVersion}</code>
+      </div>
 
-			<div class="text-right mt-8">
-				<Button fullwidth on:click={onAcknowledge}>Acknowledge</Button>
-			</div>
-		</div>
-	</FullScreenModal>
+      <div class="text-right mt-8">
+        <Button fullwidth on:click={onAcknowledge}>Acknowledge</Button>
+      </div>
+    </div>
+  </FullScreenModal>
 {/if}
diff --git a/web/src/lib/components/sharedlinks-page/shared-link-card.svelte b/web/src/lib/components/sharedlinks-page/shared-link-card.svelte
index fc9ab4806d..feaa036d13 100644
--- a/web/src/lib/components/sharedlinks-page/shared-link-card.svelte
+++ b/web/src/lib/components/sharedlinks-page/shared-link-card.svelte
@@ -1,165 +1,155 @@
 <script lang="ts">
-	import {
-		api,
-		AssetResponseDto,
-		SharedLinkResponseDto,
-		SharedLinkType,
-		ThumbnailFormat
-	} from '@api';
-	import LoadingSpinner from '../shared-components/loading-spinner.svelte';
-	import OpenInNew from 'svelte-material-icons/OpenInNew.svelte';
-	import Delete from 'svelte-material-icons/TrashCanOutline.svelte';
-	import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
-	import CircleEditOutline from 'svelte-material-icons/CircleEditOutline.svelte';
-	import * as luxon from 'luxon';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import { createEventDispatcher } from 'svelte';
-	import { goto } from '$app/navigation';
+  import { api, AssetResponseDto, SharedLinkResponseDto, SharedLinkType, ThumbnailFormat } from '@api';
+  import LoadingSpinner from '../shared-components/loading-spinner.svelte';
+  import OpenInNew from 'svelte-material-icons/OpenInNew.svelte';
+  import Delete from 'svelte-material-icons/TrashCanOutline.svelte';
+  import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
+  import CircleEditOutline from 'svelte-material-icons/CircleEditOutline.svelte';
+  import * as luxon from 'luxon';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import { createEventDispatcher } from 'svelte';
+  import { goto } from '$app/navigation';
 
-	export let link: SharedLinkResponseDto;
+  export let link: SharedLinkResponseDto;
 
-	let expirationCountdown: luxon.DurationObjectUnits;
-	const dispatch = createEventDispatcher();
+  let expirationCountdown: luxon.DurationObjectUnits;
+  const dispatch = createEventDispatcher();
 
-	const getAssetInfo = async (): Promise<AssetResponseDto> => {
-		let assetId = '';
+  const getAssetInfo = async (): Promise<AssetResponseDto> => {
+    let assetId = '';
 
-		if (link.album?.albumThumbnailAssetId) {
-			assetId = link.album.albumThumbnailAssetId;
-		} else if (link.assets.length > 0) {
-			assetId = link.assets[0].id;
-		}
+    if (link.album?.albumThumbnailAssetId) {
+      assetId = link.album.albumThumbnailAssetId;
+    } else if (link.assets.length > 0) {
+      assetId = link.assets[0].id;
+    }
 
-		const { data } = await api.assetApi.getAssetById({ id: assetId });
+    const { data } = await api.assetApi.getAssetById({ id: assetId });
 
-		return data;
-	};
+    return data;
+  };
 
-	const getCountDownExpirationDate = () => {
-		if (!link.expiresAt) {
-			return;
-		}
+  const getCountDownExpirationDate = () => {
+    if (!link.expiresAt) {
+      return;
+    }
 
-		const expiresAtDate = luxon.DateTime.fromISO(new Date(link.expiresAt).toISOString());
-		const now = luxon.DateTime.now();
+    const expiresAtDate = luxon.DateTime.fromISO(new Date(link.expiresAt).toISOString());
+    const now = luxon.DateTime.now();
 
-		expirationCountdown = expiresAtDate
-			.diff(now, ['days', 'hours', 'minutes', 'seconds'])
-			.toObject();
+    expirationCountdown = expiresAtDate.diff(now, ['days', 'hours', 'minutes', 'seconds']).toObject();
 
-		if (expirationCountdown.days && expirationCountdown.days > 0) {
-			return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'days' });
-		} else if (expirationCountdown.hours && expirationCountdown.hours > 0) {
-			return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'hours' });
-		} else if (expirationCountdown.minutes && expirationCountdown.minutes > 0) {
-			return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'minutes' });
-		} else if (expirationCountdown.seconds && expirationCountdown.seconds > 0) {
-			return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'seconds' });
-		}
-	};
+    if (expirationCountdown.days && expirationCountdown.days > 0) {
+      return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'days' });
+    } else if (expirationCountdown.hours && expirationCountdown.hours > 0) {
+      return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'hours' });
+    } else if (expirationCountdown.minutes && expirationCountdown.minutes > 0) {
+      return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'minutes' });
+    } else if (expirationCountdown.seconds && expirationCountdown.seconds > 0) {
+      return expiresAtDate.toRelativeCalendar({ base: now, locale: 'en-US', unit: 'seconds' });
+    }
+  };
 
-	const isExpired = (expiresAt: string) => {
-		const now = new Date().getTime();
-		const expiration = new Date(expiresAt).getTime();
+  const isExpired = (expiresAt: string) => {
+    const now = new Date().getTime();
+    const expiration = new Date(expiresAt).getTime();
 
-		return now > expiration;
-	};
+    return now > expiration;
+  };
 </script>
 
 <div
-	class="w-full flex gap-4 dark:text-immich-gray transition-all border-b border-gray-200 dark:border-gray-600 hover:border-immich-primary dark:hover:border-immich-dark-primary py-4"
+  class="w-full flex gap-4 dark:text-immich-gray transition-all border-b border-gray-200 dark:border-gray-600 hover:border-immich-primary dark:hover:border-immich-dark-primary py-4"
 >
-	<div>
-		{#await getAssetInfo()}
-			<LoadingSpinner />
-		{:then asset}
-			<img
-				id={asset.id}
-				src={api.getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
-				alt={asset.id}
-				class="object-cover w-[100px] h-[100px] rounded-lg"
-				loading="lazy"
-				draggable="false"
-			/>
-		{/await}
-	</div>
+  <div>
+    {#await getAssetInfo()}
+      <LoadingSpinner />
+    {:then asset}
+      <img
+        id={asset.id}
+        src={api.getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
+        alt={asset.id}
+        class="object-cover w-[100px] h-[100px] rounded-lg"
+        loading="lazy"
+        draggable="false"
+      />
+    {/await}
+  </div>
 
-	<div class="flex flex-col justify-between">
-		<div class="info-top">
-			<div class="text-xs font-mono font-semibold text-gray-500 dark:text-gray-400">
-				{#if link.expiresAt}
-					{#if isExpired(link.expiresAt)}
-						<p class="text-red-600 dark:text-red-400 font-bold">Expired</p>
-					{:else}
-						<p>
-							Expires {getCountDownExpirationDate()}
-						</p>
-					{/if}
-				{:else}
-					<p>Expires ∞</p>
-				{/if}
-			</div>
+  <div class="flex flex-col justify-between">
+    <div class="info-top">
+      <div class="text-xs font-mono font-semibold text-gray-500 dark:text-gray-400">
+        {#if link.expiresAt}
+          {#if isExpired(link.expiresAt)}
+            <p class="text-red-600 dark:text-red-400 font-bold">Expired</p>
+          {:else}
+            <p>
+              Expires {getCountDownExpirationDate()}
+            </p>
+          {/if}
+        {:else}
+          <p>Expires ∞</p>
+        {/if}
+      </div>
 
-			<div class="text-sm">
-				<div
-					class="flex gap-2 place-items-center text-immich-primary dark:text-immich-dark-primary"
-				>
-					{#if link.type === SharedLinkType.Album}
-						<p>
-							{link.album?.albumName.toUpperCase()}
-						</p>
-					{:else if link.type === SharedLinkType.Individual}
-						<p>INDIVIDUAL SHARE</p>
-					{/if}
+      <div class="text-sm">
+        <div class="flex gap-2 place-items-center text-immich-primary dark:text-immich-dark-primary">
+          {#if link.type === SharedLinkType.Album}
+            <p>
+              {link.album?.albumName.toUpperCase()}
+            </p>
+          {:else if link.type === SharedLinkType.Individual}
+            <p>INDIVIDUAL SHARE</p>
+          {/if}
 
-					{#if !link.expiresAt || !isExpired(link.expiresAt)}
-						<div
-							class="hover:cursor-pointer"
-							title="Go to share page"
-							on:click={() => goto(`/share/${link.key}`)}
-							on:keydown={() => goto(`/share/${link.key}`)}
-						>
-							<OpenInNew />
-						</div>
-					{/if}
-				</div>
+          {#if !link.expiresAt || !isExpired(link.expiresAt)}
+            <div
+              class="hover:cursor-pointer"
+              title="Go to share page"
+              on:click={() => goto(`/share/${link.key}`)}
+              on:keydown={() => goto(`/share/${link.key}`)}
+            >
+              <OpenInNew />
+            </div>
+          {/if}
+        </div>
 
-				<p class="text-sm">{link.description ?? ''}</p>
-			</div>
-		</div>
+        <p class="text-sm">{link.description ?? ''}</p>
+      </div>
+    </div>
 
-		<div class="info-bottom flex gap-4">
-			{#if link.allowUpload}
-				<div
-					class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[80px]"
-				>
-					Upload
-				</div>
-			{/if}
+    <div class="info-bottom flex gap-4">
+      {#if link.allowUpload}
+        <div
+          class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[80px]"
+        >
+          Upload
+        </div>
+      {/if}
 
-			{#if link.allowDownload}
-				<div
-					class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[100px]"
-				>
-					Download
-				</div>
-			{/if}
+      {#if link.allowDownload}
+        <div
+          class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[100px]"
+        >
+          Download
+        </div>
+      {/if}
 
-			{#if link.showExif}
-				<div
-					class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[60px]"
-				>
-					EXIF
-				</div>
-			{/if}
-		</div>
-	</div>
+      {#if link.showExif}
+        <div
+          class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[60px]"
+        >
+          EXIF
+        </div>
+      {/if}
+    </div>
+  </div>
 
-	<div class="flex-auto flex flex-col place-content-center place-items-end text-right">
-		<div class="flex">
-			<CircleIconButton logo={Delete} on:click={() => dispatch('delete')} />
-			<CircleIconButton logo={CircleEditOutline} on:click={() => dispatch('edit')} />
-			<CircleIconButton logo={ContentCopy} on:click={() => dispatch('copy')} />
-		</div>
-	</div>
+  <div class="flex-auto flex flex-col place-content-center place-items-end text-right">
+    <div class="flex">
+      <CircleIconButton logo={Delete} on:click={() => dispatch('delete')} />
+      <CircleIconButton logo={CircleEditOutline} on:click={() => dispatch('edit')} />
+      <CircleIconButton logo={ContentCopy} on:click={() => dispatch('copy')} />
+    </div>
+  </div>
 </div>
diff --git a/web/src/lib/components/user-settings-page/change-password-settings.svelte b/web/src/lib/components/user-settings-page/change-password-settings.svelte
index 9902f66706..4439d75d7d 100644
--- a/web/src/lib/components/user-settings-page/change-password-settings.svelte
+++ b/web/src/lib/components/user-settings-page/change-password-settings.svelte
@@ -1,80 +1,78 @@
 <script lang="ts">
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { api, ApiError } from '@api';
-	import { fade } from 'svelte/transition';
-	import SettingInputField, {
-		SettingInputFieldType
-	} from '../admin-page/settings/setting-input-field.svelte';
-	import Button from '../elements/buttons/button.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { api, ApiError } from '@api';
+  import { fade } from 'svelte/transition';
+  import SettingInputField, { SettingInputFieldType } from '../admin-page/settings/setting-input-field.svelte';
+  import Button from '../elements/buttons/button.svelte';
 
-	let password = '';
-	let newPassword = '';
-	let confirmPassword = '';
+  let password = '';
+  let newPassword = '';
+  let confirmPassword = '';
 
-	const handleChangePassword = async () => {
-		try {
-			await api.authenticationApi.changePassword({
-				changePasswordDto: {
-					password,
-					newPassword
-				}
-			});
+  const handleChangePassword = async () => {
+    try {
+      await api.authenticationApi.changePassword({
+        changePasswordDto: {
+          password,
+          newPassword,
+        },
+      });
 
-			notificationController.show({
-				message: 'Updated password',
-				type: NotificationType.Info
-			});
+      notificationController.show({
+        message: 'Updated password',
+        type: NotificationType.Info,
+      });
 
-			password = '';
-			newPassword = '';
-			confirmPassword = '';
-		} catch (error) {
-			console.error('Error [user-profile] [changePassword]', error);
-			notificationController.show({
-				message: (error as ApiError)?.response?.data?.message || 'Unable to change password',
-				type: NotificationType.Error
-			});
-		}
-	};
+      password = '';
+      newPassword = '';
+      confirmPassword = '';
+    } catch (error) {
+      console.error('Error [user-profile] [changePassword]', error);
+      notificationController.show({
+        message: (error as ApiError)?.response?.data?.message || 'Unable to change password',
+        type: NotificationType.Error,
+      });
+    }
+  };
 </script>
 
 <section class="my-4">
-	<div in:fade={{ duration: 500 }}>
-		<form autocomplete="off" on:submit|preventDefault>
-			<div class="flex flex-col gap-4 ml-4 mt-4">
-				<SettingInputField
-					inputType={SettingInputFieldType.PASSWORD}
-					label="PASSWORD"
-					bind:value={password}
-					required={true}
-				/>
+  <div in:fade={{ duration: 500 }}>
+    <form autocomplete="off" on:submit|preventDefault>
+      <div class="flex flex-col gap-4 ml-4 mt-4">
+        <SettingInputField
+          inputType={SettingInputFieldType.PASSWORD}
+          label="PASSWORD"
+          bind:value={password}
+          required={true}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.PASSWORD}
-					label="NEW PASSWORD"
-					bind:value={newPassword}
-					required={true}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.PASSWORD}
+          label="NEW PASSWORD"
+          bind:value={newPassword}
+          required={true}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.PASSWORD}
-					label="CONFIRM PASSWORD"
-					bind:value={confirmPassword}
-					required={true}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.PASSWORD}
+          label="CONFIRM PASSWORD"
+          bind:value={confirmPassword}
+          required={true}
+        />
 
-				<div class="flex justify-end">
-					<Button
-						type="submit"
-						size="sm"
-						disabled={!(password && newPassword && newPassword === confirmPassword)}
-						on:click={() => handleChangePassword()}>Save</Button
-					>
-				</div>
-			</div>
-		</form>
-	</div>
+        <div class="flex justify-end">
+          <Button
+            type="submit"
+            size="sm"
+            disabled={!(password && newPassword && newPassword === confirmPassword)}
+            on:click={() => handleChangePassword()}>Save</Button
+          >
+        </div>
+      </div>
+    </form>
+  </div>
 </section>
diff --git a/web/src/lib/components/user-settings-page/device-card.svelte b/web/src/lib/components/user-settings-page/device-card.svelte
index b55d54f7e4..593c0e407f 100644
--- a/web/src/lib/components/user-settings-page/device-card.svelte
+++ b/web/src/lib/components/user-settings-page/device-card.svelte
@@ -1,72 +1,70 @@
 <script lang="ts">
-	import { locale } from '$lib/stores/preferences.store';
-	import type { AuthDeviceResponseDto } from '@api';
-	import { DateTime, ToRelativeCalendarOptions } from 'luxon';
-	import { createEventDispatcher } from 'svelte';
-	import Android from 'svelte-material-icons/Android.svelte';
-	import Apple from 'svelte-material-icons/Apple.svelte';
-	import AppleSafari from 'svelte-material-icons/AppleSafari.svelte';
-	import GoogleChrome from 'svelte-material-icons/GoogleChrome.svelte';
-	import Help from 'svelte-material-icons/Help.svelte';
-	import Linux from 'svelte-material-icons/Linux.svelte';
-	import MicrosoftWindows from 'svelte-material-icons/MicrosoftWindows.svelte';
-	import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
+  import { locale } from '$lib/stores/preferences.store';
+  import type { AuthDeviceResponseDto } from '@api';
+  import { DateTime, ToRelativeCalendarOptions } from 'luxon';
+  import { createEventDispatcher } from 'svelte';
+  import Android from 'svelte-material-icons/Android.svelte';
+  import Apple from 'svelte-material-icons/Apple.svelte';
+  import AppleSafari from 'svelte-material-icons/AppleSafari.svelte';
+  import GoogleChrome from 'svelte-material-icons/GoogleChrome.svelte';
+  import Help from 'svelte-material-icons/Help.svelte';
+  import Linux from 'svelte-material-icons/Linux.svelte';
+  import MicrosoftWindows from 'svelte-material-icons/MicrosoftWindows.svelte';
+  import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
 
-	export let device: AuthDeviceResponseDto;
+  export let device: AuthDeviceResponseDto;
 
-	const dispatcher = createEventDispatcher();
+  const dispatcher = createEventDispatcher();
 
-	const options: ToRelativeCalendarOptions = {
-		unit: 'days',
-		locale: $locale
-	};
+  const options: ToRelativeCalendarOptions = {
+    unit: 'days',
+    locale: $locale,
+  };
 </script>
 
 <div class="flex flex-row w-full">
-	<!-- TODO: Device Image -->
-	<div
-		class="hidden sm:flex pr-2 justify-center items-center text-immich-primary dark:text-immich-dark-primary"
-	>
-		{#if device.deviceOS === 'Android'}
-			<Android size="40" />
-		{:else if device.deviceOS === 'iOS' || device.deviceOS === 'Mac OS'}
-			<Apple size="40" />
-		{:else if device.deviceOS.indexOf('Safari') !== -1}
-			<AppleSafari size="40" />
-		{:else if device.deviceOS.indexOf('Windows') !== -1}
-			<MicrosoftWindows size="40" />
-		{:else if device.deviceOS === 'Linux'}
-			<Linux size="40" />
-		{:else if device.deviceOS === 'Chromium OS' || device.deviceType === 'Chrome' || device.deviceType === 'Chromium'}
-			<GoogleChrome size="40" />
-		{:else}
-			<Help size="40" />
-		{/if}
-	</div>
-	<div class="pl-4 sm:pl-0 flex flex-row grow justify-between gap-1">
-		<div class="flex flex-col gap-1 justify-center dark:text-white">
-			<span class="text-sm">
-				{#if device.deviceType || device.deviceOS}
-					<span>{device.deviceOS || 'Unknown'} • {device.deviceType || 'Unknown'}</span>
-				{:else}
-					<span>Unknown</span>
-				{/if}
-			</span>
-			<div class="text-sm">
-				<span class="">Last seen</span>
-				<span>{DateTime.fromISO(device.updatedAt).toRelativeCalendar(options)}</span>
-			</div>
-		</div>
-		{#if !device.current}
-			<div class="text-sm flex flex-col justify-center">
-				<button
-					on:click={() => dispatcher('delete')}
-					class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
-					title="Log out"
-				>
-					<TrashCanOutline size="16" />
-				</button>
-			</div>
-		{/if}
-	</div>
+  <!-- TODO: Device Image -->
+  <div class="hidden sm:flex pr-2 justify-center items-center text-immich-primary dark:text-immich-dark-primary">
+    {#if device.deviceOS === 'Android'}
+      <Android size="40" />
+    {:else if device.deviceOS === 'iOS' || device.deviceOS === 'Mac OS'}
+      <Apple size="40" />
+    {:else if device.deviceOS.indexOf('Safari') !== -1}
+      <AppleSafari size="40" />
+    {:else if device.deviceOS.indexOf('Windows') !== -1}
+      <MicrosoftWindows size="40" />
+    {:else if device.deviceOS === 'Linux'}
+      <Linux size="40" />
+    {:else if device.deviceOS === 'Chromium OS' || device.deviceType === 'Chrome' || device.deviceType === 'Chromium'}
+      <GoogleChrome size="40" />
+    {:else}
+      <Help size="40" />
+    {/if}
+  </div>
+  <div class="pl-4 sm:pl-0 flex flex-row grow justify-between gap-1">
+    <div class="flex flex-col gap-1 justify-center dark:text-white">
+      <span class="text-sm">
+        {#if device.deviceType || device.deviceOS}
+          <span>{device.deviceOS || 'Unknown'} • {device.deviceType || 'Unknown'}</span>
+        {:else}
+          <span>Unknown</span>
+        {/if}
+      </span>
+      <div class="text-sm">
+        <span class="">Last seen</span>
+        <span>{DateTime.fromISO(device.updatedAt).toRelativeCalendar(options)}</span>
+      </div>
+    </div>
+    {#if !device.current}
+      <div class="text-sm flex flex-col justify-center">
+        <button
+          on:click={() => dispatcher('delete')}
+          class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
+          title="Log out"
+        >
+          <TrashCanOutline size="16" />
+        </button>
+      </div>
+    {/if}
+  </div>
 </div>
diff --git a/web/src/lib/components/user-settings-page/device-list.svelte b/web/src/lib/components/user-settings-page/device-list.svelte
index dfd85a667e..d19f1ad3cc 100644
--- a/web/src/lib/components/user-settings-page/device-list.svelte
+++ b/web/src/lib/components/user-settings-page/device-list.svelte
@@ -1,102 +1,93 @@
 <script lang="ts">
-	import { api, AuthDeviceResponseDto } from '@api';
-	import { onMount } from 'svelte';
-	import { handleError } from '../../utils/handle-error';
-	import Button from '../elements/buttons/button.svelte';
-	import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import DeviceCard from './device-card.svelte';
+  import { api, AuthDeviceResponseDto } from '@api';
+  import { onMount } from 'svelte';
+  import { handleError } from '../../utils/handle-error';
+  import Button from '../elements/buttons/button.svelte';
+  import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import DeviceCard from './device-card.svelte';
 
-	let devices: AuthDeviceResponseDto[] = [];
-	let deleteDevice: AuthDeviceResponseDto | null = null;
-	let deleteAll = false;
+  let devices: AuthDeviceResponseDto[] = [];
+  let deleteDevice: AuthDeviceResponseDto | null = null;
+  let deleteAll = false;
 
-	const refresh = () => api.authenticationApi.getAuthDevices().then(({ data }) => (devices = data));
+  const refresh = () => api.authenticationApi.getAuthDevices().then(({ data }) => (devices = data));
 
-	onMount(() => {
-		refresh();
-	});
+  onMount(() => {
+    refresh();
+  });
 
-	$: currentDevice = devices.find((device) => device.current);
-	$: otherDevices = devices.filter((device) => !device.current);
+  $: currentDevice = devices.find((device) => device.current);
+  $: otherDevices = devices.filter((device) => !device.current);
 
-	const handleDelete = async () => {
-		if (!deleteDevice) {
-			return;
-		}
+  const handleDelete = async () => {
+    if (!deleteDevice) {
+      return;
+    }
 
-		try {
-			await api.authenticationApi.logoutAuthDevice({ id: deleteDevice.id });
-			notificationController.show({ message: `Logged out device`, type: NotificationType.Info });
-		} catch (error) {
-			handleError(error, 'Unable to log out device');
-		} finally {
-			await refresh();
-			deleteDevice = null;
-		}
-	};
+    try {
+      await api.authenticationApi.logoutAuthDevice({ id: deleteDevice.id });
+      notificationController.show({ message: `Logged out device`, type: NotificationType.Info });
+    } catch (error) {
+      handleError(error, 'Unable to log out device');
+    } finally {
+      await refresh();
+      deleteDevice = null;
+    }
+  };
 
-	const handleDeleteAll = async () => {
-		try {
-			await api.authenticationApi.logoutAuthDevices();
-			notificationController.show({
-				message: `Logged out all devices`,
-				type: NotificationType.Info
-			});
-		} catch (error) {
-			handleError(error, 'Unable to log out all devices');
-		} finally {
-			await refresh();
-			deleteAll = false;
-		}
-	};
+  const handleDeleteAll = async () => {
+    try {
+      await api.authenticationApi.logoutAuthDevices();
+      notificationController.show({
+        message: `Logged out all devices`,
+        type: NotificationType.Info,
+      });
+    } catch (error) {
+      handleError(error, 'Unable to log out all devices');
+    } finally {
+      await refresh();
+      deleteAll = false;
+    }
+  };
 </script>
 
 {#if deleteDevice}
-	<ConfirmDialogue
-		prompt="Are you sure you want to log out this device?"
-		on:confirm={() => handleDelete()}
-		on:cancel={() => (deleteDevice = null)}
-	/>
+  <ConfirmDialogue
+    prompt="Are you sure you want to log out this device?"
+    on:confirm={() => handleDelete()}
+    on:cancel={() => (deleteDevice = null)}
+  />
 {/if}
 
 {#if deleteAll}
-	<ConfirmDialogue
-		prompt="Are you sure you want to log out all devices?"
-		on:confirm={() => handleDeleteAll()}
-		on:cancel={() => (deleteAll = false)}
-	/>
+  <ConfirmDialogue
+    prompt="Are you sure you want to log out all devices?"
+    on:confirm={() => handleDeleteAll()}
+    on:cancel={() => (deleteAll = false)}
+  />
 {/if}
 
 <section class="my-4">
-	{#if currentDevice}
-		<div class="mb-6">
-			<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">
-				CURRENT DEVICE
-			</h3>
-			<DeviceCard device={currentDevice} />
-		</div>
-	{/if}
-	{#if otherDevices.length > 0}
-		<div class="mb-6">
-			<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">
-				OTHER DEVICES
-			</h3>
-			{#each otherDevices as device, i}
-				<DeviceCard {device} on:delete={() => (deleteDevice = device)} />
-				{#if i !== otherDevices.length - 1}
-					<hr class="my-3" />
-				{/if}
-			{/each}
-		</div>
-		<h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">
-			LOG OUT ALL DEVICES
-		</h3>
-		<div class="flex justify-end">
-			<Button color="red" size="sm" on:click={() => (deleteAll = true)}>Log Out All Devices</Button>
-		</div>
-	{/if}
+  {#if currentDevice}
+    <div class="mb-6">
+      <h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">CURRENT DEVICE</h3>
+      <DeviceCard device={currentDevice} />
+    </div>
+  {/if}
+  {#if otherDevices.length > 0}
+    <div class="mb-6">
+      <h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">OTHER DEVICES</h3>
+      {#each otherDevices as device, i}
+        <DeviceCard {device} on:delete={() => (deleteDevice = device)} />
+        {#if i !== otherDevices.length - 1}
+          <hr class="my-3" />
+        {/if}
+      {/each}
+    </div>
+    <h3 class="font-medium text-xs mb-2 text-immich-primary dark:text-immich-dark-primary">LOG OUT ALL DEVICES</h3>
+    <div class="flex justify-end">
+      <Button color="red" size="sm" on:click={() => (deleteAll = true)}>Log Out All Devices</Button>
+    </div>
+  {/if}
 </section>
diff --git a/web/src/lib/components/user-settings-page/oauth-settings.svelte b/web/src/lib/components/user-settings-page/oauth-settings.svelte
index 384db14e8a..f6f490639a 100644
--- a/web/src/lib/components/user-settings-page/oauth-settings.svelte
+++ b/web/src/lib/components/user-settings-page/oauth-settings.svelte
@@ -1,80 +1,77 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import { oauth, OAuthConfigResponseDto, UserResponseDto } from '@api';
-	import { onMount } from 'svelte';
-	import { fade } from 'svelte/transition';
-	import { handleError } from '../../utils/handle-error';
-	import LoadingSpinner from '../shared-components/loading-spinner.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import Button from '../elements/buttons/button.svelte';
+  import { goto } from '$app/navigation';
+  import { oauth, OAuthConfigResponseDto, UserResponseDto } from '@api';
+  import { onMount } from 'svelte';
+  import { fade } from 'svelte/transition';
+  import { handleError } from '../../utils/handle-error';
+  import LoadingSpinner from '../shared-components/loading-spinner.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import Button from '../elements/buttons/button.svelte';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	let config: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: true };
-	let loading = true;
+  let config: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: true };
+  let loading = true;
 
-	onMount(async () => {
-		if (oauth.isCallback(window.location)) {
-			try {
-				loading = true;
+  onMount(async () => {
+    if (oauth.isCallback(window.location)) {
+      try {
+        loading = true;
 
-				const { data } = await oauth.link(window.location);
-				user = data;
+        const { data } = await oauth.link(window.location);
+        user = data;
 
-				notificationController.show({
-					message: 'Linked OAuth account',
-					type: NotificationType.Info
-				});
-			} catch (error) {
-				handleError(error, 'Unable to link OAuth account');
-			} finally {
-				goto('?open=oauth');
-			}
-		}
+        notificationController.show({
+          message: 'Linked OAuth account',
+          type: NotificationType.Info,
+        });
+      } catch (error) {
+        handleError(error, 'Unable to link OAuth account');
+      } finally {
+        goto('?open=oauth');
+      }
+    }
 
-		try {
-			const { data } = await oauth.getConfig(window.location);
-			config = data;
-		} catch (error) {
-			handleError(error, 'Unable to load OAuth config');
-		}
+    try {
+      const { data } = await oauth.getConfig(window.location);
+      config = data;
+    } catch (error) {
+      handleError(error, 'Unable to load OAuth config');
+    }
 
-		loading = false;
-	});
+    loading = false;
+  });
 
-	const handleUnlink = async () => {
-		try {
-			const { data } = await oauth.unlink();
-			user = data;
-			notificationController.show({
-				message: 'Unlinked OAuth account',
-				type: NotificationType.Info
-			});
-		} catch (error) {
-			handleError(error, 'Unable to unlink account');
-		}
-	};
+  const handleUnlink = async () => {
+    try {
+      const { data } = await oauth.unlink();
+      user = data;
+      notificationController.show({
+        message: 'Unlinked OAuth account',
+        type: NotificationType.Info,
+      });
+    } catch (error) {
+      handleError(error, 'Unable to unlink account');
+    }
+  };
 </script>
 
 <section class="my-4">
-	<div in:fade={{ duration: 500 }}>
-		<div class="flex justify-end">
-			{#if loading}
-				<div class="flex place-items-center place-content-center">
-					<LoadingSpinner />
-				</div>
-			{:else if config.enabled}
-				{#if user.oauthId}
-					<Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button>
-				{:else}
-					<a href={config.url}>
-						<Button size="sm" on:click={() => handleUnlink()}>Link to OAuth</Button>
-					</a>
-				{/if}
-			{/if}
-		</div>
-	</div>
+  <div in:fade={{ duration: 500 }}>
+    <div class="flex justify-end">
+      {#if loading}
+        <div class="flex place-items-center place-content-center">
+          <LoadingSpinner />
+        </div>
+      {:else if config.enabled}
+        {#if user.oauthId}
+          <Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button>
+        {:else}
+          <a href={config.url}>
+            <Button size="sm" on:click={() => handleUnlink()}>Link to OAuth</Button>
+          </a>
+        {/if}
+      {/if}
+    </div>
+  </div>
 </section>
diff --git a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte
index 2446883e7f..99c13aad04 100644
--- a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte
+++ b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte
@@ -1,87 +1,85 @@
 <script lang="ts">
-	import { api, UserResponseDto } from '@api';
-	import BaseModal from '../shared-components/base-modal.svelte';
-	import UserAvatar from '../shared-components/user-avatar.svelte';
-	import ImmichLogo from '../shared-components/immich-logo.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import { createEventDispatcher, onMount } from 'svelte';
+  import { api, UserResponseDto } from '@api';
+  import BaseModal from '../shared-components/base-modal.svelte';
+  import UserAvatar from '../shared-components/user-avatar.svelte';
+  import ImmichLogo from '../shared-components/immich-logo.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import { createEventDispatcher, onMount } from 'svelte';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	let availableUsers: UserResponseDto[] = [];
-	let selectedUsers: UserResponseDto[] = [];
+  let availableUsers: UserResponseDto[] = [];
+  let selectedUsers: UserResponseDto[] = [];
 
-	const dispatch = createEventDispatcher<{ close: void; 'add-users': UserResponseDto[] }>();
+  const dispatch = createEventDispatcher<{ close: void; 'add-users': UserResponseDto[] }>();
 
-	onMount(async () => {
-		// TODO: update endpoint to have a query param for deleted users
-		let { data: users } = await api.userApi.getAllUsers({ isAll: false });
+  onMount(async () => {
+    // TODO: update endpoint to have a query param for deleted users
+    let { data: users } = await api.userApi.getAllUsers({ isAll: false });
 
-		// remove invalid users
-		users = users.filter((_user) => !(_user.deletedAt || _user.id === user.id));
+    // remove invalid users
+    users = users.filter((_user) => !(_user.deletedAt || _user.id === user.id));
 
-		// exclude partners from the list of users available for selection
-		const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-by' });
-		const partnerIds = partners.map((partner) => partner.id);
-		availableUsers = users.filter((user) => !partnerIds.includes(user.id));
-	});
+    // exclude partners from the list of users available for selection
+    const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-by' });
+    const partnerIds = partners.map((partner) => partner.id);
+    availableUsers = users.filter((user) => !partnerIds.includes(user.id));
+  });
 
-	const selectUser = (user: UserResponseDto) => {
-		if (selectedUsers.includes(user)) {
-			selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
-		} else {
-			selectedUsers = [...selectedUsers, user];
-		}
-	};
+  const selectUser = (user: UserResponseDto) => {
+    if (selectedUsers.includes(user)) {
+      selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
+    } else {
+      selectedUsers = [...selectedUsers, user];
+    }
+  };
 </script>
 
 <BaseModal on:close={() => dispatch('close')}>
-	<svelte:fragment slot="title">
-		<span class="flex gap-2 place-items-center">
-			<ImmichLogo width={24} />
-			<p class="font-medium">Add partner</p>
-		</span>
-	</svelte:fragment>
+  <svelte:fragment slot="title">
+    <span class="flex gap-2 place-items-center">
+      <ImmichLogo width={24} />
+      <p class="font-medium">Add partner</p>
+    </span>
+  </svelte:fragment>
 
-	<div class="max-h-[300px] overflow-y-auto immich-scrollbar">
-		{#if availableUsers.length > 0}
-			{#each availableUsers as user}
-				<button
-					on:click={() => selectUser(user)}
-					class="w-full flex place-items-center gap-4 py-4 px-5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
-				>
-					{#if selectedUsers.includes(user)}
-						<span
-							class="bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-bg rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl dark:border-immich-dark-gray"
-							>✓</span
-						>
-					{:else}
-						<UserAvatar {user} size="md" autoColor />
-					{/if}
+  <div class="max-h-[300px] overflow-y-auto immich-scrollbar">
+    {#if availableUsers.length > 0}
+      {#each availableUsers as user}
+        <button
+          on:click={() => selectUser(user)}
+          class="w-full flex place-items-center gap-4 py-4 px-5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
+        >
+          {#if selectedUsers.includes(user)}
+            <span
+              class="bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-bg rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl dark:border-immich-dark-gray"
+              >✓</span
+            >
+          {:else}
+            <UserAvatar {user} size="md" autoColor />
+          {/if}
 
-					<div class="text-left">
-						<p class="text-immich-fg dark:text-immich-dark-fg">
-							{user.firstName}
-							{user.lastName}
-						</p>
-						<p class="text-xs">
-							{user.email}
-						</p>
-					</div>
-				</button>
-			{/each}
-		{:else}
-			<p class="text-sm p-5">
-				Looks like you shared your photos with all users or you don't have any user to share with.
-			</p>
-		{/if}
+          <div class="text-left">
+            <p class="text-immich-fg dark:text-immich-dark-fg">
+              {user.firstName}
+              {user.lastName}
+            </p>
+            <p class="text-xs">
+              {user.email}
+            </p>
+          </div>
+        </button>
+      {/each}
+    {:else}
+      <p class="text-sm p-5">
+        Looks like you shared your photos with all users or you don't have any user to share with.
+      </p>
+    {/if}
 
-		{#if selectedUsers.length > 0}
-			<div class="flex place-content-end p-5">
-				<Button size="sm" rounded="lg" on:click={() => dispatch('add-users', selectedUsers)}>
-					Add
-				</Button>
-			</div>
-		{/if}
-	</div>
+    {#if selectedUsers.length > 0}
+      <div class="flex place-content-end p-5">
+        <Button size="sm" rounded="lg" on:click={() => dispatch('add-users', selectedUsers)}>Add</Button>
+      </div>
+    {/if}
+  </div>
 </BaseModal>
diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte
index 7aedbfba94..a8719d911c 100644
--- a/web/src/lib/components/user-settings-page/partner-settings.svelte
+++ b/web/src/lib/components/user-settings-page/partner-settings.svelte
@@ -1,100 +1,100 @@
 <script lang="ts">
-	import { UserResponseDto, api } from '@api';
-	import UserAvatar from '../shared-components/user-avatar.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import PartnerSelectionModal from './partner-selection-modal.svelte';
-	import { handleError } from '../../utils/handle-error';
-	import { onMount } from 'svelte';
-	import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+  import { UserResponseDto, api } from '@api';
+  import UserAvatar from '../shared-components/user-avatar.svelte';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import Button from '../elements/buttons/button.svelte';
+  import PartnerSelectionModal from './partner-selection-modal.svelte';
+  import { handleError } from '../../utils/handle-error';
+  import { onMount } from 'svelte';
+  import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
+  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	let partners: UserResponseDto[] = [];
-	let createPartner = false;
-	let removePartner: UserResponseDto | null = null;
+  let partners: UserResponseDto[] = [];
+  let createPartner = false;
+  let removePartner: UserResponseDto | null = null;
 
-	const refreshPartners = async () => {
-		const { data } = await api.partnerApi.getPartners({ direction: 'shared-by' });
-		partners = data;
-	};
+  const refreshPartners = async () => {
+    const { data } = await api.partnerApi.getPartners({ direction: 'shared-by' });
+    partners = data;
+  };
 
-	const handleRemovePartner = async () => {
-		if (!removePartner) {
-			return;
-		}
+  const handleRemovePartner = async () => {
+    if (!removePartner) {
+      return;
+    }
 
-		try {
-			await api.partnerApi.removePartner({ id: removePartner.id });
-			removePartner = null;
-			await refreshPartners();
-		} catch (error) {
-			handleError(error, 'Unable to remove partner');
-		}
-	};
+    try {
+      await api.partnerApi.removePartner({ id: removePartner.id });
+      removePartner = null;
+      await refreshPartners();
+    } catch (error) {
+      handleError(error, 'Unable to remove partner');
+    }
+  };
 
-	const handleCreatePartners = async (users: UserResponseDto[]) => {
-		try {
-			for (const user of users) {
-				await api.partnerApi.createPartner({ id: user.id });
-			}
+  const handleCreatePartners = async (users: UserResponseDto[]) => {
+    try {
+      for (const user of users) {
+        await api.partnerApi.createPartner({ id: user.id });
+      }
 
-			await refreshPartners();
-			createPartner = false;
-		} catch (error) {
-			handleError(error, 'Unable to add partners');
-		}
-	};
+      await refreshPartners();
+      createPartner = false;
+    } catch (error) {
+      handleError(error, 'Unable to add partners');
+    }
+  };
 
-	onMount(async () => {
-		await refreshPartners();
-	});
+  onMount(async () => {
+    await refreshPartners();
+  });
 </script>
 
 <section class="my-4">
-	{#if partners.length > 0}
-		<div class="flex flex-row gap-4">
-			{#each partners as partner (partner.id)}
-				<div class="flex rounded-lg gap-4 py-4 px-5 transition-all">
-					<UserAvatar user={partner} size="md" autoColor />
-					<div class="text-left">
-						<p class="text-immich-fg dark:text-immich-dark-fg">
-							{partner.firstName}
-							{partner.lastName}
-						</p>
-						<p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
-							{partner.email}
-						</p>
-					</div>
-					<CircleIconButton
-						on:click={() => (removePartner = partner)}
-						logo={Close}
-						size={'16'}
-						title="Remove partner"
-					/>
-				</div>
-			{/each}
-		</div>
-	{/if}
-	<div class="flex justify-end">
-		<Button size="sm" on:click={() => (createPartner = true)}>Add partner</Button>
-	</div>
+  {#if partners.length > 0}
+    <div class="flex flex-row gap-4">
+      {#each partners as partner (partner.id)}
+        <div class="flex rounded-lg gap-4 py-4 px-5 transition-all">
+          <UserAvatar user={partner} size="md" autoColor />
+          <div class="text-left">
+            <p class="text-immich-fg dark:text-immich-dark-fg">
+              {partner.firstName}
+              {partner.lastName}
+            </p>
+            <p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
+              {partner.email}
+            </p>
+          </div>
+          <CircleIconButton
+            on:click={() => (removePartner = partner)}
+            logo={Close}
+            size={'16'}
+            title="Remove partner"
+          />
+        </div>
+      {/each}
+    </div>
+  {/if}
+  <div class="flex justify-end">
+    <Button size="sm" on:click={() => (createPartner = true)}>Add partner</Button>
+  </div>
 </section>
 
 {#if createPartner}
-	<PartnerSelectionModal
-		{user}
-		on:close={() => (createPartner = false)}
-		on:add-users={(event) => handleCreatePartners(event.detail)}
-	/>
+  <PartnerSelectionModal
+    {user}
+    on:close={() => (createPartner = false)}
+    on:add-users={(event) => handleCreatePartners(event.detail)}
+  />
 {/if}
 
 {#if removePartner}
-	<ConfirmDialogue
-		title="Stop sharing your photos?"
-		prompt="{removePartner.firstName} will no longer be able to access your photos."
-		on:cancel={() => (removePartner = null)}
-		on:confirm={() => handleRemovePartner()}
-	/>
+  <ConfirmDialogue
+    title="Stop sharing your photos?"
+    prompt="{removePartner.firstName} will no longer be able to access your photos."
+    on:cancel={() => (removePartner = null)}
+    on:confirm={() => handleRemovePartner()}
+  />
 {/if}
diff --git a/web/src/lib/components/user-settings-page/user-api-key-list.svelte b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
index 5612497ef0..129418c953 100644
--- a/web/src/lib/components/user-settings-page/user-api-key-list.svelte
+++ b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
@@ -1,177 +1,167 @@
 <script lang="ts">
-	import { api, APIKeyResponseDto } from '@api';
-	import { onMount } from 'svelte';
-	import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
-	import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
-	import { fade } from 'svelte/transition';
-	import { handleError } from '../../utils/handle-error';
-	import APIKeyForm from '../forms/api-key-form.svelte';
-	import APIKeySecret from '../forms/api-key-secret.svelte';
-	import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import { locale } from '$lib/stores/preferences.store';
-	import Button from '../elements/buttons/button.svelte';
+  import { api, APIKeyResponseDto } from '@api';
+  import { onMount } from 'svelte';
+  import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
+  import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
+  import { fade } from 'svelte/transition';
+  import { handleError } from '../../utils/handle-error';
+  import APIKeyForm from '../forms/api-key-form.svelte';
+  import APIKeySecret from '../forms/api-key-secret.svelte';
+  import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
+  import { notificationController, NotificationType } from '../shared-components/notification/notification';
+  import { locale } from '$lib/stores/preferences.store';
+  import Button from '../elements/buttons/button.svelte';
 
-	let keys: APIKeyResponseDto[] = [];
+  let keys: APIKeyResponseDto[] = [];
 
-	let newKey: Partial<APIKeyResponseDto> | null = null;
-	let editKey: APIKeyResponseDto | null = null;
-	let deleteKey: APIKeyResponseDto | null = null;
-	let secret = '';
+  let newKey: Partial<APIKeyResponseDto> | null = null;
+  let editKey: APIKeyResponseDto | null = null;
+  let deleteKey: APIKeyResponseDto | null = null;
+  let secret = '';
 
-	const format: Intl.DateTimeFormatOptions = {
-		month: 'short',
-		day: 'numeric',
-		year: 'numeric'
-	};
+  const format: Intl.DateTimeFormatOptions = {
+    month: 'short',
+    day: 'numeric',
+    year: 'numeric',
+  };
 
-	onMount(() => {
-		refreshKeys();
-	});
+  onMount(() => {
+    refreshKeys();
+  });
 
-	async function refreshKeys() {
-		const { data } = await api.keyApi.getKeys();
-		keys = data;
-	}
+  async function refreshKeys() {
+    const { data } = await api.keyApi.getKeys();
+    keys = data;
+  }
 
-	const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
-		try {
-			const dto = event.detail;
-			const { data } = await api.keyApi.createKey({ aPIKeyCreateDto: dto });
-			secret = data.secret;
-		} catch (error) {
-			handleError(error, 'Unable to create a new API Key');
-		} finally {
-			await refreshKeys();
-			newKey = null;
-		}
-	};
+  const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
+    try {
+      const dto = event.detail;
+      const { data } = await api.keyApi.createKey({ aPIKeyCreateDto: dto });
+      secret = data.secret;
+    } catch (error) {
+      handleError(error, 'Unable to create a new API Key');
+    } finally {
+      await refreshKeys();
+      newKey = null;
+    }
+  };
 
-	const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => {
-		if (!editKey) {
-			return;
-		}
+  const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => {
+    if (!editKey) {
+      return;
+    }
 
-		const dto = event.detail;
+    const dto = event.detail;
 
-		try {
-			await api.keyApi.updateKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } });
-			notificationController.show({
-				message: `Saved API Key`,
-				type: NotificationType.Info
-			});
-		} catch (error) {
-			handleError(error, 'Unable to save API Key');
-		} finally {
-			await refreshKeys();
-			editKey = null;
-		}
-	};
+    try {
+      await api.keyApi.updateKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } });
+      notificationController.show({
+        message: `Saved API Key`,
+        type: NotificationType.Info,
+      });
+    } catch (error) {
+      handleError(error, 'Unable to save API Key');
+    } finally {
+      await refreshKeys();
+      editKey = null;
+    }
+  };
 
-	const handleDelete = async () => {
-		if (!deleteKey) {
-			return;
-		}
+  const handleDelete = async () => {
+    if (!deleteKey) {
+      return;
+    }
 
-		try {
-			await api.keyApi.deleteKey({ id: deleteKey.id });
-			notificationController.show({
-				message: `Removed API Key: ${deleteKey.name}`,
-				type: NotificationType.Info
-			});
-		} catch (error) {
-			handleError(error, 'Unable to remove API Key');
-		} finally {
-			await refreshKeys();
-			deleteKey = null;
-		}
-	};
+    try {
+      await api.keyApi.deleteKey({ id: deleteKey.id });
+      notificationController.show({
+        message: `Removed API Key: ${deleteKey.name}`,
+        type: NotificationType.Info,
+      });
+    } catch (error) {
+      handleError(error, 'Unable to remove API Key');
+    } finally {
+      await refreshKeys();
+      deleteKey = null;
+    }
+  };
 </script>
 
 {#if newKey}
-	<APIKeyForm
-		title="New API Key"
-		submitText="Create"
-		apiKey={newKey}
-		on:submit={handleCreate}
-		on:cancel={() => (newKey = null)}
-	/>
+  <APIKeyForm
+    title="New API Key"
+    submitText="Create"
+    apiKey={newKey}
+    on:submit={handleCreate}
+    on:cancel={() => (newKey = null)}
+  />
 {/if}
 
 {#if secret}
-	<APIKeySecret {secret} on:done={() => (secret = '')} />
+  <APIKeySecret {secret} on:done={() => (secret = '')} />
 {/if}
 
 {#if editKey}
-	<APIKeyForm
-		submitText="Save"
-		apiKey={editKey}
-		on:submit={handleUpdate}
-		on:cancel={() => (editKey = null)}
-	/>
+  <APIKeyForm submitText="Save" apiKey={editKey} on:submit={handleUpdate} on:cancel={() => (editKey = null)} />
 {/if}
 
 {#if deleteKey}
-	<ConfirmDialogue
-		prompt="Are you sure you want to delete this API Key?"
-		on:confirm={() => handleDelete()}
-		on:cancel={() => (deleteKey = null)}
-	/>
+  <ConfirmDialogue
+    prompt="Are you sure you want to delete this API Key?"
+    on:confirm={() => handleDelete()}
+    on:cancel={() => (deleteKey = null)}
+  />
 {/if}
 
 <section class="my-4">
-	<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
-		<div class="flex justify-end mb-2">
-			<Button size="sm" on:click={() => (newKey = { name: 'API Key' })}>New API Key</Button>
-		</div>
+  <div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
+    <div class="flex justify-end mb-2">
+      <Button size="sm" on:click={() => (newKey = { name: 'API Key' })}>New API Key</Button>
+    </div>
 
-		{#if keys.length > 0}
-			<table class="text-left w-full">
-				<thead
-					class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
-				>
-					<tr class="flex w-full place-items-center">
-						<th class="text-center w-1/3 font-medium text-sm">Name</th>
-						<th class="text-center w-1/3 font-medium text-sm">Created</th>
-						<th class="text-center w-1/3 font-medium text-sm">Action</th>
-					</tr>
-				</thead>
-				<tbody class="overflow-y-auto rounded-md w-full block border dark:border-immich-dark-gray">
-					{#each keys as key, i}
-						{#key key.id}
-							<tr
-								class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
-									i % 2 == 0
-										? 'bg-immich-gray dark:bg-immich-dark-gray/75'
-										: 'bg-immich-bg dark:bg-immich-dark-gray/50'
-								}`}
-							>
-								<td class="text-sm px-4 w-1/3 text-ellipsis">{key.name}</td>
-								<td class="text-sm px-4 w-1/3 text-ellipsis"
-									>{new Date(key.createdAt).toLocaleDateString($locale, format)}
-								</td>
-								<td class="text-sm px-4 w-1/3 text-ellipsis">
-									<button
-										on:click={() => (editKey = key)}
-										class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
-									>
-										<PencilOutline size="16" />
-									</button>
-									<button
-										on:click={() => (deleteKey = key)}
-										class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
-									>
-										<TrashCanOutline size="16" />
-									</button>
-								</td>
-							</tr>
-						{/key}
-					{/each}
-				</tbody>
-			</table>
-		{/if}
-	</div>
+    {#if keys.length > 0}
+      <table class="text-left w-full">
+        <thead
+          class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
+        >
+          <tr class="flex w-full place-items-center">
+            <th class="text-center w-1/3 font-medium text-sm">Name</th>
+            <th class="text-center w-1/3 font-medium text-sm">Created</th>
+            <th class="text-center w-1/3 font-medium text-sm">Action</th>
+          </tr>
+        </thead>
+        <tbody class="overflow-y-auto rounded-md w-full block border dark:border-immich-dark-gray">
+          {#each keys as key, i}
+            {#key key.id}
+              <tr
+                class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
+                  i % 2 == 0 ? 'bg-immich-gray dark:bg-immich-dark-gray/75' : 'bg-immich-bg dark:bg-immich-dark-gray/50'
+                }`}
+              >
+                <td class="text-sm px-4 w-1/3 text-ellipsis">{key.name}</td>
+                <td class="text-sm px-4 w-1/3 text-ellipsis"
+                  >{new Date(key.createdAt).toLocaleDateString($locale, format)}
+                </td>
+                <td class="text-sm px-4 w-1/3 text-ellipsis">
+                  <button
+                    on:click={() => (editKey = key)}
+                    class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
+                  >
+                    <PencilOutline size="16" />
+                  </button>
+                  <button
+                    on:click={() => (deleteKey = key)}
+                    class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
+                  >
+                    <TrashCanOutline size="16" />
+                  </button>
+                </td>
+              </tr>
+            {/key}
+          {/each}
+        </tbody>
+      </table>
+    {/if}
+  </div>
 </section>
diff --git a/web/src/lib/components/user-settings-page/user-profile-settings.svelte b/web/src/lib/components/user-settings-page/user-profile-settings.svelte
index 847e517dbf..0c4571f608 100644
--- a/web/src/lib/components/user-settings-page/user-profile-settings.svelte
+++ b/web/src/lib/components/user-settings-page/user-profile-settings.svelte
@@ -1,92 +1,86 @@
 <script lang="ts">
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { api, UserResponseDto } from '@api';
-	import { fade } from 'svelte/transition';
-	import { handleError } from '../../utils/handle-error';
-	import SettingInputField, {
-		SettingInputFieldType
-	} from '../admin-page/settings/setting-input-field.svelte';
-	import Button from '../elements/buttons/button.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { api, UserResponseDto } from '@api';
+  import { fade } from 'svelte/transition';
+  import { handleError } from '../../utils/handle-error';
+  import SettingInputField, { SettingInputFieldType } from '../admin-page/settings/setting-input-field.svelte';
+  import Button from '../elements/buttons/button.svelte';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	const handleSaveProfile = async () => {
-		try {
-			const { data } = await api.userApi.updateUser({
-				updateUserDto: {
-					id: user.id,
-					email: user.email,
-					firstName: user.firstName,
-					lastName: user.lastName
-				}
-			});
+  const handleSaveProfile = async () => {
+    try {
+      const { data } = await api.userApi.updateUser({
+        updateUserDto: {
+          id: user.id,
+          email: user.email,
+          firstName: user.firstName,
+          lastName: user.lastName,
+        },
+      });
 
-			Object.assign(user, data);
+      Object.assign(user, data);
 
-			notificationController.show({
-				message: 'Saved profile',
-				type: NotificationType.Info
-			});
-		} catch (error) {
-			handleError(error, 'Unable to save profile');
-		}
-	};
+      notificationController.show({
+        message: 'Saved profile',
+        type: NotificationType.Info,
+      });
+    } catch (error) {
+      handleError(error, 'Unable to save profile');
+    }
+  };
 </script>
 
 <section class="my-4">
-	<div in:fade={{ duration: 500 }}>
-		<form autocomplete="off" on:submit|preventDefault>
-			<div class="flex flex-col gap-4 ml-4 mt-4">
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="USER ID"
-					bind:value={user.id}
-					disabled={true}
-				/>
+  <div in:fade={{ duration: 500 }}>
+    <form autocomplete="off" on:submit|preventDefault>
+      <div class="flex flex-col gap-4 ml-4 mt-4">
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="USER ID"
+          bind:value={user.id}
+          disabled={true}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.EMAIL}
-					label="EMAIL"
-					bind:value={user.email}
-				/>
+        <SettingInputField inputType={SettingInputFieldType.EMAIL} label="EMAIL" bind:value={user.email} />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="FIRST NAME"
-					bind:value={user.firstName}
-					required={true}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="FIRST NAME"
+          bind:value={user.firstName}
+          required={true}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="LAST NAME"
-					bind:value={user.lastName}
-					required={true}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="LAST NAME"
+          bind:value={user.lastName}
+          required={true}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="STORAGE LABEL"
-					disabled={true}
-					value={user.storageLabel || ''}
-					required={false}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="STORAGE LABEL"
+          disabled={true}
+          value={user.storageLabel || ''}
+          required={false}
+        />
 
-				<SettingInputField
-					inputType={SettingInputFieldType.TEXT}
-					label="EXTERNAL PATH"
-					disabled={true}
-					value={user.externalPath || ''}
-					required={false}
-				/>
+        <SettingInputField
+          inputType={SettingInputFieldType.TEXT}
+          label="EXTERNAL PATH"
+          disabled={true}
+          value={user.externalPath || ''}
+          required={false}
+        />
 
-				<div class="flex justify-end">
-					<Button type="submit" size="sm" on:click={() => handleSaveProfile()}>Save</Button>
-				</div>
-			</div>
-		</form>
-	</div>
+        <div class="flex justify-end">
+          <Button type="submit" size="sm" on:click={() => handleSaveProfile()}>Save</Button>
+        </div>
+      </div>
+    </form>
+  </div>
 </section>
diff --git a/web/src/lib/components/user-settings-page/user-settings-list.svelte b/web/src/lib/components/user-settings-page/user-settings-list.svelte
index 6f069f664d..76311414c3 100644
--- a/web/src/lib/components/user-settings-page/user-settings-list.svelte
+++ b/web/src/lib/components/user-settings-page/user-settings-list.svelte
@@ -1,58 +1,58 @@
 <script lang="ts">
-	import { page } from '$app/stores';
-	import { oauth, UserResponseDto } from '@api';
-	import { onMount } from 'svelte';
-	import SettingAccordion from '../admin-page/settings/setting-accordion.svelte';
-	import ChangePasswordSettings from './change-password-settings.svelte';
-	import OAuthSettings from './oauth-settings.svelte';
-	import UserAPIKeyList from './user-api-key-list.svelte';
-	import DeviceList from './device-list.svelte';
-	import PartnerSettings from './partner-settings.svelte';
-	import UserProfileSettings from './user-profile-settings.svelte';
+  import { page } from '$app/stores';
+  import { oauth, UserResponseDto } from '@api';
+  import { onMount } from 'svelte';
+  import SettingAccordion from '../admin-page/settings/setting-accordion.svelte';
+  import ChangePasswordSettings from './change-password-settings.svelte';
+  import OAuthSettings from './oauth-settings.svelte';
+  import UserAPIKeyList from './user-api-key-list.svelte';
+  import DeviceList from './device-list.svelte';
+  import PartnerSettings from './partner-settings.svelte';
+  import UserProfileSettings from './user-profile-settings.svelte';
 
-	export let user: UserResponseDto;
+  export let user: UserResponseDto;
 
-	let oauthEnabled = false;
-	let oauthOpen = false;
+  let oauthEnabled = false;
+  let oauthOpen = false;
 
-	onMount(async () => {
-		oauthOpen = oauth.isCallback(window.location);
+  onMount(async () => {
+    oauthOpen = oauth.isCallback(window.location);
 
-		try {
-			const { data } = await oauth.getConfig(window.location);
-			oauthEnabled = data.enabled;
-		} catch {
-			// noop
-		}
-	});
+    try {
+      const { data } = await oauth.getConfig(window.location);
+      oauthEnabled = data.enabled;
+    } catch {
+      // noop
+    }
+  });
 </script>
 
 <SettingAccordion title="Account" subtitle="Manage your account">
-	<UserProfileSettings {user} />
+  <UserProfileSettings {user} />
 </SettingAccordion>
 
 <SettingAccordion title="API Keys" subtitle="Manage your API keys">
-	<UserAPIKeyList />
+  <UserAPIKeyList />
 </SettingAccordion>
 
 <SettingAccordion title="Authorized Devices" subtitle="Manage your logged-in devices">
-	<DeviceList />
+  <DeviceList />
 </SettingAccordion>
 
 {#if oauthEnabled}
-	<SettingAccordion
-		title="OAuth"
-		subtitle="Manage your OAuth connection"
-		isOpen={oauthOpen || $page.url.searchParams.get('open') === 'oauth'}
-	>
-		<OAuthSettings {user} />
-	</SettingAccordion>
+  <SettingAccordion
+    title="OAuth"
+    subtitle="Manage your OAuth connection"
+    isOpen={oauthOpen || $page.url.searchParams.get('open') === 'oauth'}
+  >
+    <OAuthSettings {user} />
+  </SettingAccordion>
 {/if}
 
 <SettingAccordion title="Password" subtitle="Change your password">
-	<ChangePasswordSettings />
+  <ChangePasswordSettings />
 </SettingAccordion>
 
 <SettingAccordion title="Sharing" subtitle="Manage sharing with partners">
-	<PartnerSettings {user} />
+  <PartnerSettings {user} />
 </SettingAccordion>
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index cc3bc635d8..1e715fa250 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -2,26 +2,26 @@ import { env } from '$env/dynamic/public';
 export const loginPageMessage: string | undefined = env.PUBLIC_LOGIN_PAGE_MESSAGE;
 
 export enum AppRoute {
-	ADMIN_USER_MANAGEMENT = '/admin/user-management',
-	ADMIN_SETTINGS = '/admin/system-settings',
-	ADMIN_STATS = '/admin/server-status',
-	ADMIN_JOBS = '/admin/jobs-status',
+  ADMIN_USER_MANAGEMENT = '/admin/user-management',
+  ADMIN_SETTINGS = '/admin/system-settings',
+  ADMIN_STATS = '/admin/server-status',
+  ADMIN_JOBS = '/admin/jobs-status',
 
-	ALBUMS = '/albums',
-	ARCHIVE = '/archive',
-	FAVORITES = '/favorites',
-	PEOPLE = '/people',
-	PHOTOS = '/photos',
-	EXPLORE = '/explore',
-	SHARING = '/sharing',
-	SHARED_LINKS = '/sharing/sharedlinks',
-	SEARCH = '/search',
-	MAP = '/map',
-	USER_SETTINGS = '/user-settings',
-	MEMORY = '/memory',
+  ALBUMS = '/albums',
+  ARCHIVE = '/archive',
+  FAVORITES = '/favorites',
+  PEOPLE = '/people',
+  PHOTOS = '/photos',
+  EXPLORE = '/explore',
+  SHARING = '/sharing',
+  SHARED_LINKS = '/sharing/sharedlinks',
+  SEARCH = '/search',
+  MAP = '/map',
+  USER_SETTINGS = '/user-settings',
+  MEMORY = '/memory',
 
-	AUTH_LOGIN = '/auth/login',
-	AUTH_LOGOUT = '/auth/logout',
-	AUTH_REGISTER = '/auth/register',
-	AUTH_CHANGE_PASSWORD = '/auth/change-password'
+  AUTH_LOGIN = '/auth/login',
+  AUTH_LOGOUT = '/auth/logout',
+  AUTH_REGISTER = '/auth/register',
+  AUTH_CHANGE_PASSWORD = '/auth/change-password',
 }
diff --git a/web/src/lib/models/asset-grid-state.ts b/web/src/lib/models/asset-grid-state.ts
index 1f3eb26ab1..fcf5da767e 100644
--- a/web/src/lib/models/asset-grid-state.ts
+++ b/web/src/lib/models/asset-grid-state.ts
@@ -1,53 +1,53 @@
 import type { AssetResponseDto } from '@api';
 
 export enum BucketPosition {
-	Above = 'above',
-	Below = 'below',
-	Visible = 'visible',
-	Unknown = 'unknown'
+  Above = 'above',
+  Below = 'below',
+  Visible = 'visible',
+  Unknown = 'unknown',
 }
 
 export class AssetBucket {
-	/**
-	 * The DOM height of the bucket in pixel
-	 * This value is first estimated by the number of asset and later is corrected as the user scroll
-	 */
-	bucketHeight!: number;
-	bucketDate!: string;
-	assets!: AssetResponseDto[];
-	cancelToken!: AbortController;
-	position!: BucketPosition;
+  /**
+   * The DOM height of the bucket in pixel
+   * This value is first estimated by the number of asset and later is corrected as the user scroll
+   */
+  bucketHeight!: number;
+  bucketDate!: string;
+  assets!: AssetResponseDto[];
+  cancelToken!: AbortController;
+  position!: BucketPosition;
 }
 
 export class AssetGridState {
-	/**
-	 * The total height of the timeline in pixel
-	 * This value is first estimated by the number of asset and later is corrected as the user scroll
-	 */
-	timelineHeight = 0;
+  /**
+   * The total height of the timeline in pixel
+   * This value is first estimated by the number of asset and later is corrected as the user scroll
+   */
+  timelineHeight = 0;
 
-	/**
-	 * The fixed viewport height in pixel
-	 */
-	viewportHeight = 0;
+  /**
+   * The fixed viewport height in pixel
+   */
+  viewportHeight = 0;
 
-	/**
-	 * The fixed viewport width in pixel
-	 */
-	viewportWidth = 0;
+  /**
+   * The fixed viewport width in pixel
+   */
+  viewportWidth = 0;
 
-	/**
-	 * List of bucket information
-	 */
-	buckets: AssetBucket[] = [];
+  /**
+   * List of bucket information
+   */
+  buckets: AssetBucket[] = [];
 
-	/**
-	 * Total assets that have been loaded
-	 */
-	assets: AssetResponseDto[] = [];
+  /**
+   * Total assets that have been loaded
+   */
+  assets: AssetResponseDto[] = [];
 
-	/**
-	 * User that owns assets
-	 */
-	userId: string | undefined;
+  /**
+   * User that owns assets
+   */
+  userId: string | undefined;
 }
diff --git a/web/src/lib/models/upload-asset.ts b/web/src/lib/models/upload-asset.ts
index 6079251b63..c4de5458b8 100644
--- a/web/src/lib/models/upload-asset.ts
+++ b/web/src/lib/models/upload-asset.ts
@@ -1,6 +1,6 @@
 export type UploadAsset = {
-	id: string;
-	file: File;
-	progress: number;
-	fileExtension: string;
+  id: string;
+  file: File;
+  progress: number;
+  fileExtension: string;
 };
diff --git a/web/src/lib/stores/album-asset-selection.store.ts b/web/src/lib/stores/album-asset-selection.store.ts
index 18cf42eb2d..a99e2baaca 100644
--- a/web/src/lib/stores/album-asset-selection.store.ts
+++ b/web/src/lib/stores/album-asset-selection.store.ts
@@ -1,10 +1,10 @@
 import { writable } from 'svelte/store';
 
 function createAlbumAssetSelectionStore() {
-	const isAlbumAssetSelectionOpen = writable<boolean>(false);
-	return {
-		isAlbumAssetSelectionOpen
-	};
+  const isAlbumAssetSelectionOpen = writable<boolean>(false);
+  return {
+    isAlbumAssetSelectionOpen,
+  };
 }
 
 export const albumAssetSelectionStore = createAlbumAssetSelectionStore();
diff --git a/web/src/lib/stores/asset-interaction.store.ts b/web/src/lib/stores/asset-interaction.store.ts
index ec7dbb169f..a49f7f4bc2 100644
--- a/web/src/lib/stores/asset-interaction.store.ts
+++ b/web/src/lib/stores/asset-interaction.store.ts
@@ -1,8 +1,8 @@
 import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
 import { api, AssetResponseDto } from '@api';
+import { sortBy } from 'lodash-es';
 import { derived, writable } from 'svelte/store';
 import { assetGridState, assetStore } from './assets.store';
-import { sortBy } from 'lodash-es';
 
 // Asset Viewer
 export const viewingAssetStoreState = writable<AssetResponseDto>();
@@ -12,147 +12,144 @@ export const isViewingAssetStoreState = writable<boolean>(false);
 export const assetsInAlbumStoreState = writable<AssetResponseDto[]>([]);
 export const selectedAssets = writable<Set<AssetResponseDto>>(new Set());
 export const selectedGroup = writable<Set<string>>(new Set());
-export const isMultiSelectStoreState = derived(
-	selectedAssets,
-	($selectedAssets) => $selectedAssets.size > 0
-);
+export const isMultiSelectStoreState = derived(selectedAssets, ($selectedAssets) => $selectedAssets.size > 0);
 
 function createAssetInteractionStore() {
-	let _assetGridState = new AssetGridState();
-	let _viewingAssetStoreState: AssetResponseDto;
-	let _selectedAssets: Set<AssetResponseDto>;
-	let _selectedGroup: Set<string>;
-	let _assetsInAlbums: AssetResponseDto[];
-	let savedAssetLength = 0;
-	let assetSortedByDate: AssetResponseDto[] = [];
+  let _assetGridState = new AssetGridState();
+  let _viewingAssetStoreState: AssetResponseDto;
+  let _selectedAssets: Set<AssetResponseDto>;
+  let _selectedGroup: Set<string>;
+  let _assetsInAlbums: AssetResponseDto[];
+  let savedAssetLength = 0;
+  let assetSortedByDate: AssetResponseDto[] = [];
 
-	// Subscriber
-	assetGridState.subscribe((state) => {
-		_assetGridState = state;
-	});
+  // Subscriber
+  assetGridState.subscribe((state) => {
+    _assetGridState = state;
+  });
 
-	viewingAssetStoreState.subscribe((asset) => {
-		_viewingAssetStoreState = asset;
-	});
+  viewingAssetStoreState.subscribe((asset) => {
+    _viewingAssetStoreState = asset;
+  });
 
-	selectedAssets.subscribe((assets) => {
-		_selectedAssets = assets;
-	});
+  selectedAssets.subscribe((assets) => {
+    _selectedAssets = assets;
+  });
 
-	selectedGroup.subscribe((group) => {
-		_selectedGroup = group;
-	});
+  selectedGroup.subscribe((group) => {
+    _selectedGroup = group;
+  });
 
-	assetsInAlbumStoreState.subscribe((assets) => {
-		_assetsInAlbums = assets;
-	});
+  assetsInAlbumStoreState.subscribe((assets) => {
+    _assetsInAlbums = assets;
+  });
 
-	// Methods
+  // Methods
 
-	/**
-	 * Asset Viewer
-	 */
-	const setViewingAsset = async (asset: AssetResponseDto) => {
-		setViewingAssetId(asset.id);
-	};
+  /**
+   * Asset Viewer
+   */
+  const setViewingAsset = async (asset: AssetResponseDto) => {
+    setViewingAssetId(asset.id);
+  };
 
-	const setViewingAssetId = async (id: string) => {
-		const { data } = await api.assetApi.getAssetById({ id });
-		viewingAssetStoreState.set(data);
-		isViewingAssetStoreState.set(true);
-	};
+  const setViewingAssetId = async (id: string) => {
+    const { data } = await api.assetApi.getAssetById({ id });
+    viewingAssetStoreState.set(data);
+    isViewingAssetStoreState.set(true);
+  };
 
-	const setIsViewingAsset = (isViewing: boolean) => {
-		isViewingAssetStoreState.set(isViewing);
-	};
+  const setIsViewingAsset = (isViewing: boolean) => {
+    isViewingAssetStoreState.set(isViewing);
+  };
 
-	const navigateAsset = async (direction: 'next' | 'previous') => {
-		// Flatten and sort the asset by date if there are new assets
-		if (assetSortedByDate.length === 0 || savedAssetLength !== _assetGridState.assets.length) {
-			assetSortedByDate = sortBy(_assetGridState.assets, (a) => a.fileCreatedAt);
-			savedAssetLength = _assetGridState.assets.length;
-		}
+  const navigateAsset = async (direction: 'next' | 'previous') => {
+    // Flatten and sort the asset by date if there are new assets
+    if (assetSortedByDate.length === 0 || savedAssetLength !== _assetGridState.assets.length) {
+      assetSortedByDate = sortBy(_assetGridState.assets, (a) => a.fileCreatedAt);
+      savedAssetLength = _assetGridState.assets.length;
+    }
 
-		// Find the index of the current asset
-		const currentIndex = assetSortedByDate.findIndex((a) => a.id === _viewingAssetStoreState.id);
+    // Find the index of the current asset
+    const currentIndex = assetSortedByDate.findIndex((a) => a.id === _viewingAssetStoreState.id);
 
-		// Get the next or previous asset
-		const nextIndex = direction === 'previous' ? currentIndex + 1 : currentIndex - 1;
+    // Get the next or previous asset
+    const nextIndex = direction === 'previous' ? currentIndex + 1 : currentIndex - 1;
 
-		// Run out of asset, this might be because there is no asset in the next bucket.
-		if (nextIndex == -1) {
-			let nextBucket = '';
-			// Find next bucket that doesn't have all assets loaded
+    // Run out of asset, this might be because there is no asset in the next bucket.
+    if (nextIndex == -1) {
+      let nextBucket = '';
+      // Find next bucket that doesn't have all assets loaded
 
-			for (const bucket of _assetGridState.buckets) {
-				if (bucket.assets.length === 0) {
-					nextBucket = bucket.bucketDate;
-					break;
-				}
-			}
+      for (const bucket of _assetGridState.buckets) {
+        if (bucket.assets.length === 0) {
+          nextBucket = bucket.bucketDate;
+          break;
+        }
+      }
 
-			if (nextBucket !== '') {
-				await assetStore.getAssetsByBucket(nextBucket, BucketPosition.Below);
-				navigateAsset(direction);
-			}
-			return;
-		}
+      if (nextBucket !== '') {
+        await assetStore.getAssetsByBucket(nextBucket, BucketPosition.Below);
+        navigateAsset(direction);
+      }
+      return;
+    }
 
-		const nextAsset = assetSortedByDate[nextIndex];
-		if (nextAsset) {
-			setViewingAsset(nextAsset);
-		}
-	};
+    const nextAsset = assetSortedByDate[nextIndex];
+    if (nextAsset) {
+      setViewingAsset(nextAsset);
+    }
+  };
 
-	/**
-	 * Multiselect
-	 */
-	const addAssetToMultiselectGroup = (asset: AssetResponseDto) => {
-		// Not select if in album already
-		if (_assetsInAlbums.find((a) => a.id === asset.id)) {
-			return;
-		}
+  /**
+   * Multiselect
+   */
+  const addAssetToMultiselectGroup = (asset: AssetResponseDto) => {
+    // Not select if in album already
+    if (_assetsInAlbums.find((a) => a.id === asset.id)) {
+      return;
+    }
 
-		_selectedAssets.add(asset);
-		selectedAssets.set(_selectedAssets);
-	};
+    _selectedAssets.add(asset);
+    selectedAssets.set(_selectedAssets);
+  };
 
-	const removeAssetFromMultiselectGroup = (asset: AssetResponseDto) => {
-		_selectedAssets.delete(asset);
-		selectedAssets.set(_selectedAssets);
-	};
+  const removeAssetFromMultiselectGroup = (asset: AssetResponseDto) => {
+    _selectedAssets.delete(asset);
+    selectedAssets.set(_selectedAssets);
+  };
 
-	const addGroupToMultiselectGroup = (group: string) => {
-		_selectedGroup.add(group);
-		selectedGroup.set(_selectedGroup);
-	};
+  const addGroupToMultiselectGroup = (group: string) => {
+    _selectedGroup.add(group);
+    selectedGroup.set(_selectedGroup);
+  };
 
-	const removeGroupFromMultiselectGroup = (group: string) => {
-		_selectedGroup.delete(group);
-		selectedGroup.set(_selectedGroup);
-	};
+  const removeGroupFromMultiselectGroup = (group: string) => {
+    _selectedGroup.delete(group);
+    selectedGroup.set(_selectedGroup);
+  };
 
-	const clearMultiselect = () => {
-		_selectedAssets.clear();
-		_selectedGroup.clear();
-		_assetsInAlbums = [];
+  const clearMultiselect = () => {
+    _selectedAssets.clear();
+    _selectedGroup.clear();
+    _assetsInAlbums = [];
 
-		selectedAssets.set(_selectedAssets);
-		selectedGroup.set(_selectedGroup);
-		assetsInAlbumStoreState.set(_assetsInAlbums);
-	};
+    selectedAssets.set(_selectedAssets);
+    selectedGroup.set(_selectedGroup);
+    assetsInAlbumStoreState.set(_assetsInAlbums);
+  };
 
-	return {
-		setViewingAsset,
-		setViewingAssetId,
-		setIsViewingAsset,
-		navigateAsset,
-		addAssetToMultiselectGroup,
-		removeAssetFromMultiselectGroup,
-		addGroupToMultiselectGroup,
-		removeGroupFromMultiselectGroup,
-		clearMultiselect
-	};
+  return {
+    setViewingAsset,
+    setViewingAssetId,
+    setIsViewingAsset,
+    navigateAsset,
+    addAssetToMultiselectGroup,
+    removeAssetFromMultiselectGroup,
+    addGroupToMultiselectGroup,
+    removeGroupFromMultiselectGroup,
+    clearMultiselect,
+  };
 }
 
 export const assetInteractionStore = createAssetInteractionStore();
diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts
index eac4e18463..13475dba6a 100644
--- a/web/src/lib/stores/assets.store.ts
+++ b/web/src/lib/stores/assets.store.ts
@@ -1,6 +1,6 @@
 import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
-import { AssetCountByTimeBucketResponseDto, api } from '@api';
-import { sumBy, flatMap } from 'lodash-es';
+import { api, AssetCountByTimeBucketResponseDto } from '@api';
+import { flatMap, sumBy } from 'lodash-es';
 import { writable } from 'svelte/store';
 
 /**
@@ -10,189 +10,189 @@ export const assetGridState = writable<AssetGridState>(new AssetGridState());
 export const loadingBucketState = writable<{ [key: string]: boolean }>({});
 
 function createAssetStore() {
-	let _assetGridState = new AssetGridState();
-	assetGridState.subscribe((state) => {
-		_assetGridState = state;
-	});
+  let _assetGridState = new AssetGridState();
+  assetGridState.subscribe((state) => {
+    _assetGridState = state;
+  });
 
-	let _loadingBucketState: { [key: string]: boolean } = {};
-	loadingBucketState.subscribe((state) => {
-		_loadingBucketState = state;
-	});
+  let _loadingBucketState: { [key: string]: boolean } = {};
+  loadingBucketState.subscribe((state) => {
+    _loadingBucketState = state;
+  });
 
-	const estimateViewportHeight = (assetCount: number, viewportWidth: number): number => {
-		// Ideally we would use the average aspect ratio for the photoset, however assume
-		// a normal landscape aspect ratio of 3:2, then discount for the likelihood we
-		// will be scaling down and coalescing.
-		const thumbnailHeight = 235;
-		const unwrappedWidth = (3 / 2) * assetCount * thumbnailHeight * (7 / 10);
-		const rows = Math.ceil(unwrappedWidth / viewportWidth);
-		const height = rows * thumbnailHeight;
-		return height;
-	};
+  const estimateViewportHeight = (assetCount: number, viewportWidth: number): number => {
+    // Ideally we would use the average aspect ratio for the photoset, however assume
+    // a normal landscape aspect ratio of 3:2, then discount for the likelihood we
+    // will be scaling down and coalescing.
+    const thumbnailHeight = 235;
+    const unwrappedWidth = (3 / 2) * assetCount * thumbnailHeight * (7 / 10);
+    const rows = Math.ceil(unwrappedWidth / viewportWidth);
+    const height = rows * thumbnailHeight;
+    return height;
+  };
 
-	/**
-	 * Set initial state
-	 * @param viewportHeight
-	 * @param viewportWidth
-	 * @param data
-	 */
-	const setInitialState = (
-		viewportHeight: number,
-		viewportWidth: number,
-		data: AssetCountByTimeBucketResponseDto,
-		userId: string | undefined
-	) => {
-		assetGridState.set({
-			viewportHeight,
-			viewportWidth,
-			timelineHeight: 0,
-			buckets: data.buckets.map((bucket) => ({
-				bucketDate: bucket.timeBucket,
-				bucketHeight: estimateViewportHeight(bucket.count, viewportWidth),
-				assets: [],
-				cancelToken: new AbortController(),
-				position: BucketPosition.Unknown
-			})),
-			assets: [],
-			userId
-		});
+  /**
+   * Set initial state
+   * @param viewportHeight
+   * @param viewportWidth
+   * @param data
+   */
+  const setInitialState = (
+    viewportHeight: number,
+    viewportWidth: number,
+    data: AssetCountByTimeBucketResponseDto,
+    userId: string | undefined,
+  ) => {
+    assetGridState.set({
+      viewportHeight,
+      viewportWidth,
+      timelineHeight: 0,
+      buckets: data.buckets.map((bucket) => ({
+        bucketDate: bucket.timeBucket,
+        bucketHeight: estimateViewportHeight(bucket.count, viewportWidth),
+        assets: [],
+        cancelToken: new AbortController(),
+        position: BucketPosition.Unknown,
+      })),
+      assets: [],
+      userId,
+    });
 
-		// Update timeline height based on calculated bucket height
-		assetGridState.update((state) => {
-			state.timelineHeight = sumBy(state.buckets, (d) => d.bucketHeight);
-			return state;
-		});
-	};
+    // Update timeline height based on calculated bucket height
+    assetGridState.update((state) => {
+      state.timelineHeight = sumBy(state.buckets, (d) => d.bucketHeight);
+      return state;
+    });
+  };
 
-	const getAssetsByBucket = async (bucket: string, position: BucketPosition) => {
-		try {
-			const currentBucketData = _assetGridState.buckets.find((b) => b.bucketDate === bucket);
-			if (currentBucketData?.assets && currentBucketData.assets.length > 0) {
-				assetGridState.update((state) => {
-					const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
-					state.buckets[bucketIndex].position = position;
-					return state;
-				});
-				return;
-			}
+  const getAssetsByBucket = async (bucket: string, position: BucketPosition) => {
+    try {
+      const currentBucketData = _assetGridState.buckets.find((b) => b.bucketDate === bucket);
+      if (currentBucketData?.assets && currentBucketData.assets.length > 0) {
+        assetGridState.update((state) => {
+          const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
+          state.buckets[bucketIndex].position = position;
+          return state;
+        });
+        return;
+      }
 
-			loadingBucketState.set({
-				..._loadingBucketState,
-				[bucket]: true
-			});
-			const { data: assets } = await api.assetApi.getAssetByTimeBucket(
-				{
-					getAssetByTimeBucketDto: {
-						timeBucket: [bucket],
-						userId: _assetGridState.userId,
-						withoutThumbs: true
-					}
-				},
-				{ signal: currentBucketData?.cancelToken.signal }
-			);
-			loadingBucketState.set({
-				..._loadingBucketState,
-				[bucket]: false
-			});
+      loadingBucketState.set({
+        ..._loadingBucketState,
+        [bucket]: true,
+      });
+      const { data: assets } = await api.assetApi.getAssetByTimeBucket(
+        {
+          getAssetByTimeBucketDto: {
+            timeBucket: [bucket],
+            userId: _assetGridState.userId,
+            withoutThumbs: true,
+          },
+        },
+        { signal: currentBucketData?.cancelToken.signal },
+      );
+      loadingBucketState.set({
+        ..._loadingBucketState,
+        [bucket]: false,
+      });
 
-			// Update assetGridState with assets by time bucket
-			assetGridState.update((state) => {
-				const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
-				state.buckets[bucketIndex].assets = assets;
-				state.buckets[bucketIndex].position = position;
-				state.assets = flatMap(state.buckets, (b) => b.assets);
-				return state;
-			});
-			// eslint-disable-next-line @typescript-eslint/no-explicit-any
-		} catch (e: any) {
-			if (e.name === 'CanceledError') {
-				return;
-			}
-			console.error('Failed to get asset for bucket ', bucket);
-			console.error(e);
-		}
-	};
+      // Update assetGridState with assets by time bucket
+      assetGridState.update((state) => {
+        const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
+        state.buckets[bucketIndex].assets = assets;
+        state.buckets[bucketIndex].position = position;
+        state.assets = flatMap(state.buckets, (b) => b.assets);
+        return state;
+      });
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    } catch (e: any) {
+      if (e.name === 'CanceledError') {
+        return;
+      }
+      console.error('Failed to get asset for bucket ', bucket);
+      console.error(e);
+    }
+  };
 
-	const removeAsset = (assetId: string) => {
-		assetGridState.update((state) => {
-			const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
-			const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
-			state.buckets[bucketIndex].assets.splice(assetIndex, 1);
+  const removeAsset = (assetId: string) => {
+    assetGridState.update((state) => {
+      const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
+      const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
+      state.buckets[bucketIndex].assets.splice(assetIndex, 1);
 
-			if (state.buckets[bucketIndex].assets.length === 0) {
-				_removeBucket(state.buckets[bucketIndex].bucketDate);
-			}
-			state.assets = flatMap(state.buckets, (b) => b.assets);
-			return state;
-		});
-	};
+      if (state.buckets[bucketIndex].assets.length === 0) {
+        _removeBucket(state.buckets[bucketIndex].bucketDate);
+      }
+      state.assets = flatMap(state.buckets, (b) => b.assets);
+      return state;
+    });
+  };
 
-	const _removeBucket = (bucketDate: string) => {
-		assetGridState.update((state) => {
-			const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
-			state.buckets.splice(bucketIndex, 1);
-			state.assets = flatMap(state.buckets, (b) => b.assets);
-			return state;
-		});
-	};
+  const _removeBucket = (bucketDate: string) => {
+    assetGridState.update((state) => {
+      const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
+      state.buckets.splice(bucketIndex, 1);
+      state.assets = flatMap(state.buckets, (b) => b.assets);
+      return state;
+    });
+  };
 
-	const updateBucketHeight = (bucket: string, actualBucketHeight: number): number => {
-		let scrollTimeline = false;
-		let heightDelta = 0;
+  const updateBucketHeight = (bucket: string, actualBucketHeight: number): number => {
+    let scrollTimeline = false;
+    let heightDelta = 0;
 
-		assetGridState.update((state) => {
-			const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
-			// Update timeline height based on the new bucket height
-			const estimateBucketHeight = state.buckets[bucketIndex].bucketHeight;
+    assetGridState.update((state) => {
+      const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
+      // Update timeline height based on the new bucket height
+      const estimateBucketHeight = state.buckets[bucketIndex].bucketHeight;
 
-			heightDelta = actualBucketHeight - estimateBucketHeight;
-			state.timelineHeight += heightDelta;
+      heightDelta = actualBucketHeight - estimateBucketHeight;
+      state.timelineHeight += heightDelta;
 
-			scrollTimeline = state.buckets[bucketIndex].position == BucketPosition.Above;
+      scrollTimeline = state.buckets[bucketIndex].position == BucketPosition.Above;
 
-			state.buckets[bucketIndex].bucketHeight = actualBucketHeight;
-			state.buckets[bucketIndex].position = BucketPosition.Unknown;
+      state.buckets[bucketIndex].bucketHeight = actualBucketHeight;
+      state.buckets[bucketIndex].position = BucketPosition.Unknown;
 
-			return state;
-		});
+      return state;
+    });
 
-		if (scrollTimeline) {
-			return heightDelta;
-		}
+    if (scrollTimeline) {
+      return heightDelta;
+    }
 
-		return 0;
-	};
+    return 0;
+  };
 
-	const cancelBucketRequest = async (token: AbortController, bucketDate: string) => {
-		token.abort();
-		// set new abort controller for bucket
-		assetGridState.update((state) => {
-			const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
-			state.buckets[bucketIndex].cancelToken = new AbortController();
-			return state;
-		});
-	};
+  const cancelBucketRequest = async (token: AbortController, bucketDate: string) => {
+    token.abort();
+    // set new abort controller for bucket
+    assetGridState.update((state) => {
+      const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
+      state.buckets[bucketIndex].cancelToken = new AbortController();
+      return state;
+    });
+  };
 
-	const updateAsset = (assetId: string, isFavorite: boolean) => {
-		assetGridState.update((state) => {
-			const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
-			const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
-			state.buckets[bucketIndex].assets[assetIndex].isFavorite = isFavorite;
+  const updateAsset = (assetId: string, isFavorite: boolean) => {
+    assetGridState.update((state) => {
+      const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
+      const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
+      state.buckets[bucketIndex].assets[assetIndex].isFavorite = isFavorite;
 
-			state.assets = flatMap(state.buckets, (b) => b.assets);
-			return state;
-		});
-	};
+      state.assets = flatMap(state.buckets, (b) => b.assets);
+      return state;
+    });
+  };
 
-	return {
-		setInitialState,
-		getAssetsByBucket,
-		removeAsset,
-		updateBucketHeight,
-		cancelBucketRequest,
-		updateAsset
-	};
+  return {
+    setInitialState,
+    getAssetsByBucket,
+    removeAsset,
+    updateBucketHeight,
+    cancelBucketRequest,
+    updateAsset,
+  };
 }
 
 export const assetStore = createAssetStore();
diff --git a/web/src/lib/stores/download.ts b/web/src/lib/stores/download.ts
index e0a5d10c64..a7a9f81c07 100644
--- a/web/src/lib/stores/download.ts
+++ b/web/src/lib/stores/download.ts
@@ -1,25 +1,25 @@
-import { writable, derived } from 'svelte/store';
+import { derived, writable } from 'svelte/store';
 
 export const downloadAssets = writable<Record<string, number>>({});
 
 export const isDownloading = derived(downloadAssets, ($downloadAssets) => {
-	if (Object.keys($downloadAssets).length == 0) {
-		return false;
-	}
+  if (Object.keys($downloadAssets).length == 0) {
+    return false;
+  }
 
-	return true;
+  return true;
 });
 
 const update = (key: string, value: number | null) => {
-	downloadAssets.update((state) => {
-		const newState = { ...state };
-		if (value === null) {
-			delete newState[key];
-		} else {
-			newState[key] = value;
-		}
-		return newState;
-	});
+  downloadAssets.update((state) => {
+    const newState = { ...state };
+    if (value === null) {
+      delete newState[key];
+    } else {
+      newState[key] = value;
+    }
+    return newState;
+  });
 };
 
 export const clearDownload = (key: string) => update(key, null);
diff --git a/web/src/lib/stores/drag-and-drop-files.store.ts b/web/src/lib/stores/drag-and-drop-files.store.ts
index 018687f260..1b8733aa8b 100644
--- a/web/src/lib/stores/drag-and-drop-files.store.ts
+++ b/web/src/lib/stores/drag-and-drop-files.store.ts
@@ -2,6 +2,6 @@
 import { writable } from 'svelte/store';
 
 export const dragAndDropFilesStore = writable({
-	isDragging: false as boolean,
-	files: [] as File[]
+  isDragging: false as boolean,
+  files: [] as File[],
 });
diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts
index 7969838ce0..b70af7664a 100644
--- a/web/src/lib/stores/preferences.store.ts
+++ b/web/src/lib/stores/preferences.store.ts
@@ -1,39 +1,38 @@
 import { browser } from '$app/environment';
 import { persisted } from 'svelte-local-storage-store';
 
-const initialTheme =
-	browser && !window.matchMedia('(prefers-color-scheme: dark)').matches ? 'light' : 'dark';
+const initialTheme = browser && !window.matchMedia('(prefers-color-scheme: dark)').matches ? 'light' : 'dark';
 
 // The 'color-theme' key is also used by app.html to prevent FOUC on page load.
 export const colorTheme = persisted<'dark' | 'light'>('color-theme', initialTheme, {
-	serializer: {
-		parse: (text) => (text === 'light' ? text : 'dark'),
-		stringify: (obj) => obj
-	}
+  serializer: {
+    parse: (text) => (text === 'light' ? text : 'dark'),
+    stringify: (obj) => obj,
+  },
 });
 
 // Locale to use for formatting dates, numbers, etc.
 export const locale = persisted<string | undefined>('locale', undefined, {
-	serializer: {
-		parse: (text) => text,
-		stringify: (obj) => obj ?? ''
-	}
+  serializer: {
+    parse: (text) => text,
+    stringify: (obj) => obj ?? '',
+  },
 });
 
 export interface MapSettings {
-	allowDarkMode: boolean;
-	onlyFavorites: boolean;
-	relativeDate: string;
-	dateAfter: string;
-	dateBefore: string;
+  allowDarkMode: boolean;
+  onlyFavorites: boolean;
+  relativeDate: string;
+  dateAfter: string;
+  dateBefore: string;
 }
 
 export const mapSettings = persisted<MapSettings>('map-settings', {
-	allowDarkMode: true,
-	onlyFavorites: false,
-	relativeDate: '',
-	dateAfter: '',
-	dateBefore: ''
+  allowDarkMode: true,
+  onlyFavorites: false,
+  relativeDate: '',
+  dateAfter: '',
+  dateBefore: '',
 });
 
 export const videoViewerVolume = persisted<number>('video-viewer-volume', 1, {});
@@ -41,9 +40,9 @@ export const videoViewerVolume = persisted<number>('video-viewer-volume', 1, {})
 export const isShowDetail = persisted<boolean>('info-opened', false, {});
 
 export interface AlbumViewSettings {
-	sortBy: string;
+  sortBy: string;
 }
 
 export const albumViewSettings = persisted<AlbumViewSettings>('album-view-settings', {
-	sortBy: 'Most recent photo'
+  sortBy: 'Most recent photo',
 });
diff --git a/web/src/lib/stores/upload.ts b/web/src/lib/stores/upload.ts
index d3b7a7f0e7..0d8d31cfaf 100644
--- a/web/src/lib/stores/upload.ts
+++ b/web/src/lib/stores/upload.ts
@@ -1,45 +1,45 @@
-import { writable, derived } from 'svelte/store';
+import { derived, writable } from 'svelte/store';
 import type { UploadAsset } from '../models/upload-asset';
 
 function createUploadStore() {
-	const uploadAssets = writable<Array<UploadAsset>>([]);
+  const uploadAssets = writable<Array<UploadAsset>>([]);
 
-	const { subscribe } = uploadAssets;
+  const { subscribe } = uploadAssets;
 
-	const isUploading = derived(uploadAssets, ($uploadAssets) => {
-		return $uploadAssets.length > 0 ? true : false;
-	});
+  const isUploading = derived(uploadAssets, ($uploadAssets) => {
+    return $uploadAssets.length > 0 ? true : false;
+  });
 
-	const addNewUploadAsset = (newAsset: UploadAsset) => {
-		uploadAssets.update((currentSet) => [...currentSet, newAsset]);
-	};
+  const addNewUploadAsset = (newAsset: UploadAsset) => {
+    uploadAssets.update((currentSet) => [...currentSet, newAsset]);
+  };
 
-	const updateProgress = (id: string, progress: number) => {
-		uploadAssets.update((uploadingAssets) => {
-			return uploadingAssets.map((asset) => {
-				if (asset.id == id) {
-					return {
-						...asset,
-						progress: progress
-					};
-				}
+  const updateProgress = (id: string, progress: number) => {
+    uploadAssets.update((uploadingAssets) => {
+      return uploadingAssets.map((asset) => {
+        if (asset.id == id) {
+          return {
+            ...asset,
+            progress: progress,
+          };
+        }
 
-				return asset;
-			});
-		});
-	};
+        return asset;
+      });
+    });
+  };
 
-	const removeUploadAsset = (id: string) => {
-		uploadAssets.update((uploadingAsset) => uploadingAsset.filter((a) => a.id != id));
-	};
+  const removeUploadAsset = (id: string) => {
+    uploadAssets.update((uploadingAsset) => uploadingAsset.filter((a) => a.id != id));
+  };
 
-	return {
-		subscribe,
-		isUploading,
-		addNewUploadAsset,
-		updateProgress,
-		removeUploadAsset
-	};
+  return {
+    subscribe,
+    isUploading,
+    addNewUploadAsset,
+    updateProgress,
+    removeUploadAsset,
+  };
 }
 
 export const uploadAssetsStore = createUploadStore();
diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts
index 74e323cb7b..09777891e5 100644
--- a/web/src/lib/stores/websocket.ts
+++ b/web/src/lib/stores/websocket.ts
@@ -1,32 +1,32 @@
-import { Socket, io } from 'socket.io-client';
+import { io, Socket } from 'socket.io-client';
 
 let websocket: Socket;
 
 export const openWebsocketConnection = () => {
-	try {
-		websocket = io('', {
-			path: '/api/socket.io',
-			transports: ['polling'],
-			reconnection: true,
-			forceNew: true,
-			autoConnect: true
-		});
+  try {
+    websocket = io('', {
+      path: '/api/socket.io',
+      transports: ['polling'],
+      reconnection: true,
+      forceNew: true,
+      autoConnect: true,
+    });
 
-		listenToEvent(websocket);
-	} catch (e) {
-		console.log('Cannot connect to websocket ', e);
-	}
+    listenToEvent(websocket);
+  } catch (e) {
+    console.log('Cannot connect to websocket ', e);
+  }
 };
 
 const listenToEvent = (socket: Socket) => {
-	//TODO: if we are not using this, we should probably remove it?
-	socket.on('on_upload_success', () => undefined);
+  //TODO: if we are not using this, we should probably remove it?
+  socket.on('on_upload_success', () => undefined);
 
-	socket.on('error', (e) => {
-		console.log('Websocket Error', e);
-	});
+  socket.on('error', (e) => {
+    console.log('Websocket Error', e);
+  });
 };
 
 export const closeWebsocketConnection = () => {
-	websocket?.close();
+  websocket?.close();
 };
diff --git a/web/src/lib/stores/zoom-image.store.ts b/web/src/lib/stores/zoom-image.store.ts
index cdad99ab70..2c6ee18972 100644
--- a/web/src/lib/stores/zoom-image.store.ts
+++ b/web/src/lib/stores/zoom-image.store.ts
@@ -1,4 +1,4 @@
-import { writable } from 'svelte/store';
 import type { ZoomImageWheelState } from '@zoom-image/core';
+import { writable } from 'svelte/store';
 
 export const photoZoomState = writable<ZoomImageWheelState>();
diff --git a/web/src/lib/utils/asset-utils.spec.ts b/web/src/lib/utils/asset-utils.spec.ts
index 9105420e39..d9abbdcdc1 100644
--- a/web/src/lib/utils/asset-utils.spec.ts
+++ b/web/src/lib/utils/asset-utils.spec.ts
@@ -1,145 +1,144 @@
 import type { AssetResponseDto } from '@api';
 import { describe, expect, it } from '@jest/globals';
-import { getAssetFilename, getFilenameExtension, getFileMimeType } from './asset-utils';
+import { getAssetFilename, getFileMimeType, getFilenameExtension } from './asset-utils';
 
 describe('get file extension from filename', () => {
-	it('returns the extension without including the dot', () => {
-		expect(getFilenameExtension('filename.txt')).toEqual('txt');
-	});
+  it('returns the extension without including the dot', () => {
+    expect(getFilenameExtension('filename.txt')).toEqual('txt');
+  });
 
-	it('takes the last file extension and ignores the rest', () => {
-		expect(getFilenameExtension('filename.txt.pdf')).toEqual('pdf');
-		expect(getFilenameExtension('filename.txt.pdf.jpg')).toEqual('jpg');
-	});
+  it('takes the last file extension and ignores the rest', () => {
+    expect(getFilenameExtension('filename.txt.pdf')).toEqual('pdf');
+    expect(getFilenameExtension('filename.txt.pdf.jpg')).toEqual('jpg');
+  });
 
-	it('returns an empty string when no file extension is found', () => {
-		expect(getFilenameExtension('filename')).toEqual('');
-		expect(getFilenameExtension('filename.')).toEqual('');
-		expect(getFilenameExtension('filename..')).toEqual('');
-		expect(getFilenameExtension('.filename')).toEqual('');
-	});
+  it('returns an empty string when no file extension is found', () => {
+    expect(getFilenameExtension('filename')).toEqual('');
+    expect(getFilenameExtension('filename.')).toEqual('');
+    expect(getFilenameExtension('filename..')).toEqual('');
+    expect(getFilenameExtension('.filename')).toEqual('');
+  });
 
-	it('returns the extension from a filepath', () => {
-		expect(getFilenameExtension('/folder/file.txt')).toEqual('txt');
-		expect(getFilenameExtension('./folder/file.txt')).toEqual('txt');
-		expect(getFilenameExtension('~/folder/file.txt')).toEqual('txt');
-		expect(getFilenameExtension('./folder/.file.txt')).toEqual('txt');
-		expect(getFilenameExtension('/folder.with.dots/file.txt')).toEqual('txt');
-	});
+  it('returns the extension from a filepath', () => {
+    expect(getFilenameExtension('/folder/file.txt')).toEqual('txt');
+    expect(getFilenameExtension('./folder/file.txt')).toEqual('txt');
+    expect(getFilenameExtension('~/folder/file.txt')).toEqual('txt');
+    expect(getFilenameExtension('./folder/.file.txt')).toEqual('txt');
+    expect(getFilenameExtension('/folder.with.dots/file.txt')).toEqual('txt');
+  });
 });
 
 describe('get asset filename', () => {
-	it('returns the filename including file extension', () => {
-		[
-			{
-				asset: {
-					originalFileName: 'filename',
-					originalPath: 'upload/library/test/2016/2016-08-30/filename.jpg'
-				},
-				result: 'filename.jpg'
-			},
-			{
-				asset: {
-					originalFileName: 'new-filename',
-					originalPath:
-						'upload/library/89d14e47-a40d-4cae-a347-a914cdef1f22/2016/2016-08-30/filename.jpg'
-				},
-				result: 'new-filename.jpg'
-			},
-			{
-				asset: {
-					originalFileName: 'new-filename.txt',
-					originalPath: 'upload/library/test/2016/2016-08-30/filename.txt.jpg'
-				},
-				result: 'new-filename.txt.jpg'
-			}
-		].forEach(({ asset, result }) => {
-			expect(getAssetFilename(asset as AssetResponseDto)).toEqual(result);
-		});
-	});
+  it('returns the filename including file extension', () => {
+    [
+      {
+        asset: {
+          originalFileName: 'filename',
+          originalPath: 'upload/library/test/2016/2016-08-30/filename.jpg',
+        },
+        result: 'filename.jpg',
+      },
+      {
+        asset: {
+          originalFileName: 'new-filename',
+          originalPath: 'upload/library/89d14e47-a40d-4cae-a347-a914cdef1f22/2016/2016-08-30/filename.jpg',
+        },
+        result: 'new-filename.jpg',
+      },
+      {
+        asset: {
+          originalFileName: 'new-filename.txt',
+          originalPath: 'upload/library/test/2016/2016-08-30/filename.txt.jpg',
+        },
+        result: 'new-filename.txt.jpg',
+      },
+    ].forEach(({ asset, result }) => {
+      expect(getAssetFilename(asset as AssetResponseDto)).toEqual(result);
+    });
+  });
 });
 
 describe('get file mime type', () => {
-	for (const { mimetype, extension } of [
-		{ mimetype: 'image/avif', extension: 'avif' },
-		{ mimetype: 'image/gif', extension: 'gif' },
-		{ mimetype: 'image/heic', extension: 'heic' },
-		{ mimetype: 'image/heif', extension: 'heif' },
-		{ mimetype: 'image/jpeg', extension: 'jpeg' },
-		{ mimetype: 'image/jpeg', extension: 'jpg' },
-		{ mimetype: 'image/jxl', extension: 'jxl' },
-		{ mimetype: 'image/png', extension: 'png' },
-		{ mimetype: 'image/tiff', extension: 'tiff' },
-		{ mimetype: 'image/webp', extension: 'webp' },
-		{ mimetype: 'image/x-adobe-dng', extension: 'dng' },
-		{ mimetype: 'image/x-arriflex-ari', extension: 'ari' },
-		{ mimetype: 'image/x-canon-cr2', extension: 'cr2' },
-		{ mimetype: 'image/x-canon-cr3', extension: 'cr3' },
-		{ mimetype: 'image/x-canon-crw', extension: 'crw' },
-		{ mimetype: 'image/x-epson-erf', extension: 'erf' },
-		{ mimetype: 'image/x-fuji-raf', extension: 'raf' },
-		{ mimetype: 'image/x-hasselblad-3fr', extension: '3fr' },
-		{ mimetype: 'image/x-hasselblad-fff', extension: 'fff' },
-		{ mimetype: 'image/x-kodak-dcr', extension: 'dcr' },
-		{ mimetype: 'image/x-kodak-k25', extension: 'k25' },
-		{ mimetype: 'image/x-kodak-kdc', extension: 'kdc' },
-		{ mimetype: 'image/x-leica-rwl', extension: 'rwl' },
-		{ mimetype: 'image/x-minolta-mrw', extension: 'mrw' },
-		{ mimetype: 'image/x-nikon-nef', extension: 'nef' },
-		{ mimetype: 'image/x-olympus-orf', extension: 'orf' },
-		{ mimetype: 'image/x-olympus-ori', extension: 'ori' },
-		{ mimetype: 'image/x-panasonic-raw', extension: 'raw' },
-		{ mimetype: 'image/x-pentax-pef', extension: 'pef' },
-		{ mimetype: 'image/x-phantom-cin', extension: 'cin' },
-		{ mimetype: 'image/x-phaseone-cap', extension: 'cap' },
-		{ mimetype: 'image/x-phaseone-iiq', extension: 'iiq' },
-		{ mimetype: 'image/x-samsung-srw', extension: 'srw' },
-		{ mimetype: 'image/x-sigma-x3f', extension: 'x3f' },
-		{ mimetype: 'image/x-sony-arw', extension: 'arw' },
-		{ mimetype: 'image/x-sony-sr2', extension: 'sr2' },
-		{ mimetype: 'image/x-sony-srf', extension: 'srf' },
-		{ mimetype: 'video/3gpp', extension: '3gp' },
-		{ mimetype: 'video/avi', extension: 'avi' },
-		{ mimetype: 'video/mp2t', extension: 'm2ts' },
-		{ mimetype: 'video/mp2t', extension: 'mts' },
-		{ mimetype: 'video/mp4', extension: 'mp4' },
-		{ mimetype: 'video/mpeg', extension: 'mpg' },
-		{ mimetype: 'video/quicktime', extension: 'mov' },
-		{ mimetype: 'video/webm', extension: 'webm' },
-		{ mimetype: 'video/x-flv', extension: 'flv' },
-		{ mimetype: 'video/x-matroska', extension: 'mkv' },
-		{ mimetype: 'video/x-ms-wmv', extension: 'wmv' }
-	]) {
-		it(`returns the mime type for ${extension}`, () => {
-			expect(getFileMimeType({ name: `filename.${extension}` } as File)).toEqual(mimetype);
-		});
-	}
+  for (const { mimetype, extension } of [
+    { mimetype: 'image/avif', extension: 'avif' },
+    { mimetype: 'image/gif', extension: 'gif' },
+    { mimetype: 'image/heic', extension: 'heic' },
+    { mimetype: 'image/heif', extension: 'heif' },
+    { mimetype: 'image/jpeg', extension: 'jpeg' },
+    { mimetype: 'image/jpeg', extension: 'jpg' },
+    { mimetype: 'image/jxl', extension: 'jxl' },
+    { mimetype: 'image/png', extension: 'png' },
+    { mimetype: 'image/tiff', extension: 'tiff' },
+    { mimetype: 'image/webp', extension: 'webp' },
+    { mimetype: 'image/x-adobe-dng', extension: 'dng' },
+    { mimetype: 'image/x-arriflex-ari', extension: 'ari' },
+    { mimetype: 'image/x-canon-cr2', extension: 'cr2' },
+    { mimetype: 'image/x-canon-cr3', extension: 'cr3' },
+    { mimetype: 'image/x-canon-crw', extension: 'crw' },
+    { mimetype: 'image/x-epson-erf', extension: 'erf' },
+    { mimetype: 'image/x-fuji-raf', extension: 'raf' },
+    { mimetype: 'image/x-hasselblad-3fr', extension: '3fr' },
+    { mimetype: 'image/x-hasselblad-fff', extension: 'fff' },
+    { mimetype: 'image/x-kodak-dcr', extension: 'dcr' },
+    { mimetype: 'image/x-kodak-k25', extension: 'k25' },
+    { mimetype: 'image/x-kodak-kdc', extension: 'kdc' },
+    { mimetype: 'image/x-leica-rwl', extension: 'rwl' },
+    { mimetype: 'image/x-minolta-mrw', extension: 'mrw' },
+    { mimetype: 'image/x-nikon-nef', extension: 'nef' },
+    { mimetype: 'image/x-olympus-orf', extension: 'orf' },
+    { mimetype: 'image/x-olympus-ori', extension: 'ori' },
+    { mimetype: 'image/x-panasonic-raw', extension: 'raw' },
+    { mimetype: 'image/x-pentax-pef', extension: 'pef' },
+    { mimetype: 'image/x-phantom-cin', extension: 'cin' },
+    { mimetype: 'image/x-phaseone-cap', extension: 'cap' },
+    { mimetype: 'image/x-phaseone-iiq', extension: 'iiq' },
+    { mimetype: 'image/x-samsung-srw', extension: 'srw' },
+    { mimetype: 'image/x-sigma-x3f', extension: 'x3f' },
+    { mimetype: 'image/x-sony-arw', extension: 'arw' },
+    { mimetype: 'image/x-sony-sr2', extension: 'sr2' },
+    { mimetype: 'image/x-sony-srf', extension: 'srf' },
+    { mimetype: 'video/3gpp', extension: '3gp' },
+    { mimetype: 'video/avi', extension: 'avi' },
+    { mimetype: 'video/mp2t', extension: 'm2ts' },
+    { mimetype: 'video/mp2t', extension: 'mts' },
+    { mimetype: 'video/mp4', extension: 'mp4' },
+    { mimetype: 'video/mpeg', extension: 'mpg' },
+    { mimetype: 'video/quicktime', extension: 'mov' },
+    { mimetype: 'video/webm', extension: 'webm' },
+    { mimetype: 'video/x-flv', extension: 'flv' },
+    { mimetype: 'video/x-matroska', extension: 'mkv' },
+    { mimetype: 'video/x-ms-wmv', extension: 'wmv' },
+  ]) {
+    it(`returns the mime type for ${extension}`, () => {
+      expect(getFileMimeType({ name: `filename.${extension}` } as File)).toEqual(mimetype);
+    });
+  }
 
-	it('returns the mime type from the file', () => {
-		[
-			{
-				file: {
-					name: 'filename.jpg',
-					type: 'image/jpeg'
-				},
-				result: 'image/jpeg'
-			},
-			{
-				file: {
-					name: 'filename.txt',
-					type: 'text/plain'
-				},
-				result: 'text/plain'
-			},
-			{
-				file: {
-					name: 'filename.txt',
-					type: ''
-				},
-				result: ''
-			}
-		].forEach(({ file, result }) => {
-			expect(getFileMimeType(file as File)).toEqual(result);
-		});
-	});
+  it('returns the mime type from the file', () => {
+    [
+      {
+        file: {
+          name: 'filename.jpg',
+          type: 'image/jpeg',
+        },
+        result: 'image/jpeg',
+      },
+      {
+        file: {
+          name: 'filename.txt',
+          type: 'text/plain',
+        },
+        result: 'text/plain',
+      },
+      {
+        file: {
+          name: 'filename.txt',
+          type: '',
+        },
+        result: '',
+      },
+    ].forEach(({ file, result }) => {
+      expect(getFileMimeType(file as File)).toEqual(result);
+    });
+  });
 });
diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts
index af90d2a456..13c7c26b09 100644
--- a/web/src/lib/utils/asset-utils.ts
+++ b/web/src/lib/utils/asset-utils.ts
@@ -1,133 +1,121 @@
-import {
-	notificationController,
-	NotificationType
-} from '$lib/components/shared-components/notification/notification';
+import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
 import { clearDownload, updateDownload } from '$lib/stores/download';
-import {
-	AddAssetsResponseDto,
-	api,
-	AssetApiGetDownloadInfoRequest,
-	AssetResponseDto,
-	DownloadResponseDto
-} from '@api';
+import { AddAssetsResponseDto, api, AssetApiGetDownloadInfoRequest, AssetResponseDto, DownloadResponseDto } from '@api';
 import { handleError } from './handle-error';
 
 export const addAssetsToAlbum = async (
-	albumId: string,
-	assetIds: Array<string>,
-	key: string | undefined = undefined
+  albumId: string,
+  assetIds: Array<string>,
+  key: string | undefined = undefined,
 ): Promise<AddAssetsResponseDto> =>
-	api.albumApi
-		.addAssetsToAlbum({ id: albumId, addAssetsDto: { assetIds }, key })
-		.then(({ data: dto }) => {
-			if (dto.successfullyAdded > 0) {
-				// This might be 0 if the user tries to add an asset that is already in the album
-				notificationController.show({
-					message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`,
-					type: NotificationType.Info
-				});
-			}
+  api.albumApi.addAssetsToAlbum({ id: albumId, addAssetsDto: { assetIds }, key }).then(({ data: dto }) => {
+    if (dto.successfullyAdded > 0) {
+      // This might be 0 if the user tries to add an asset that is already in the album
+      notificationController.show({
+        message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`,
+        type: NotificationType.Info,
+      });
+    }
 
-			return dto;
-		});
+    return dto;
+  });
 
 const downloadBlob = (data: Blob, filename: string) => {
-	const url = URL.createObjectURL(data);
+  const url = URL.createObjectURL(data);
 
-	const anchor = document.createElement('a');
-	anchor.href = url;
-	anchor.download = filename;
+  const anchor = document.createElement('a');
+  anchor.href = url;
+  anchor.download = filename;
 
-	document.body.appendChild(anchor);
-	anchor.click();
-	document.body.removeChild(anchor);
+  document.body.appendChild(anchor);
+  anchor.click();
+  document.body.removeChild(anchor);
 
-	URL.revokeObjectURL(url);
+  URL.revokeObjectURL(url);
 };
 
 export const downloadArchive = async (
-	fileName: string,
-	options: Omit<AssetApiGetDownloadInfoRequest, 'key'>,
-	onDone?: () => void,
-	key?: string
+  fileName: string,
+  options: Omit<AssetApiGetDownloadInfoRequest, 'key'>,
+  onDone?: () => void,
+  key?: string,
 ) => {
-	let downloadInfo: DownloadResponseDto | null = null;
+  let downloadInfo: DownloadResponseDto | null = null;
 
-	try {
-		const { data } = await api.assetApi.getDownloadInfo({ ...options, key });
-		downloadInfo = data;
-	} catch (error) {
-		handleError(error, 'Unable to download files');
-		return;
-	}
+  try {
+    const { data } = await api.assetApi.getDownloadInfo({ ...options, key });
+    downloadInfo = data;
+  } catch (error) {
+    handleError(error, 'Unable to download files');
+    return;
+  }
 
-	// TODO: prompt for big download
-	// const total = downloadInfo.totalSize;
+  // TODO: prompt for big download
+  // const total = downloadInfo.totalSize;
 
-	for (let i = 0; i < downloadInfo.archives.length; i++) {
-		const archive = downloadInfo.archives[i];
-		const suffix = downloadInfo.archives.length === 1 ? '' : `+${i + 1}`;
-		const archiveName = fileName.replace('.zip', `${suffix}.zip`);
+  for (let i = 0; i < downloadInfo.archives.length; i++) {
+    const archive = downloadInfo.archives[i];
+    const suffix = downloadInfo.archives.length === 1 ? '' : `+${i + 1}`;
+    const archiveName = fileName.replace('.zip', `${suffix}.zip`);
 
-		let downloadKey = `${archiveName}`;
-		if (downloadInfo.archives.length > 1) {
-			downloadKey = `${archiveName} (${i + 1}/${downloadInfo.archives.length})`;
-		}
+    let downloadKey = `${archiveName}`;
+    if (downloadInfo.archives.length > 1) {
+      downloadKey = `${archiveName} (${i + 1}/${downloadInfo.archives.length})`;
+    }
 
-		updateDownload(downloadKey, 0);
+    updateDownload(downloadKey, 0);
 
-		try {
-			const { data } = await api.assetApi.downloadArchive(
-				{ assetIdsDto: { assetIds: archive.assetIds }, key },
-				{
-					responseType: 'blob',
-					onDownloadProgress: (event) =>
-						updateDownload(downloadKey, Math.floor((event.loaded / archive.size) * 100))
-				}
-			);
+    try {
+      const { data } = await api.assetApi.downloadArchive(
+        { assetIdsDto: { assetIds: archive.assetIds }, key },
+        {
+          responseType: 'blob',
+          onDownloadProgress: (event) => updateDownload(downloadKey, Math.floor((event.loaded / archive.size) * 100)),
+        },
+      );
 
-			downloadBlob(data, archiveName);
-		} catch (e) {
-			handleError(e, 'Unable to download files');
-			clearDownload(downloadKey);
-			return;
-		} finally {
-			setTimeout(() => clearDownload(downloadKey), 3_000);
-		}
-	}
+      downloadBlob(data, archiveName);
+    } catch (e) {
+      handleError(e, 'Unable to download files');
+      clearDownload(downloadKey);
+      return;
+    } finally {
+      setTimeout(() => clearDownload(downloadKey), 3_000);
+    }
+  }
 
-	onDone?.();
+  onDone?.();
 };
 
 export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
-	const filenames = [`${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`];
-	if (asset.livePhotoVideoId) {
-		filenames.push(`${asset.originalFileName}.mov`);
-	}
+  const filenames = [`${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`];
+  if (asset.livePhotoVideoId) {
+    filenames.push(`${asset.originalFileName}.mov`);
+  }
 
-	for (const filename of filenames) {
-		try {
-			updateDownload(filename, 0);
+  for (const filename of filenames) {
+    try {
+      updateDownload(filename, 0);
 
-			const { data } = await api.assetApi.downloadFile(
-				{ id: asset.id, key },
-				{
-					responseType: 'blob',
-					onDownloadProgress: (event: ProgressEvent) => {
-						if (event.lengthComputable) {
-							updateDownload(filename, Math.floor((event.loaded / event.total) * 100));
-						}
-					}
-				}
-			);
+      const { data } = await api.assetApi.downloadFile(
+        { id: asset.id, key },
+        {
+          responseType: 'blob',
+          onDownloadProgress: (event: ProgressEvent) => {
+            if (event.lengthComputable) {
+              updateDownload(filename, Math.floor((event.loaded / event.total) * 100));
+            }
+          },
+        },
+      );
 
-			downloadBlob(data, filename);
-		} catch (e) {
-			handleError(e, `Error downloading ${filename}`);
-		} finally {
-			setTimeout(() => clearDownload(filename), 3_000);
-		}
-	}
+      downloadBlob(data, filename);
+    } catch (e) {
+      handleError(e, `Error downloading ${filename}`);
+    } finally {
+      setTimeout(() => clearDownload(filename), 3_000);
+    }
+  }
 };
 
 /**
@@ -135,98 +123,98 @@ export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
  * an empty string when not found.
  */
 export function getFilenameExtension(filename: string): string {
-	const lastIndex = Math.max(0, filename.lastIndexOf('.'));
-	const startIndex = (lastIndex || Infinity) + 1;
-	return filename.slice(startIndex).toLowerCase();
+  const lastIndex = Math.max(0, filename.lastIndexOf('.'));
+  const startIndex = (lastIndex || Infinity) + 1;
+  return filename.slice(startIndex).toLowerCase();
 }
 
 /**
  * Returns the filename of an asset including file extension
  */
 export function getAssetFilename(asset: AssetResponseDto): string {
-	const fileExtension = getFilenameExtension(asset.originalPath);
-	return `${asset.originalFileName}.${fileExtension}`;
+  const fileExtension = getFilenameExtension(asset.originalPath);
+  return `${asset.originalFileName}.${fileExtension}`;
 }
 
 /**
  * Returns the MIME type of the file and an empty string when not found.
  */
 export function getFileMimeType(file: File): string {
-	const mimeTypes: Record<string, string> = {
-		'3fr': 'image/x-hasselblad-3fr',
-		'3gp': 'video/3gpp',
-		ari: 'image/x-arriflex-ari',
-		arw: 'image/x-sony-arw',
-		avi: 'video/avi',
-		avif: 'image/avif',
-		cap: 'image/x-phaseone-cap',
-		cin: 'image/x-phantom-cin',
-		cr2: 'image/x-canon-cr2',
-		cr3: 'image/x-canon-cr3',
-		crw: 'image/x-canon-crw',
-		dcr: 'image/x-kodak-dcr',
-		dng: 'image/x-adobe-dng',
-		erf: 'image/x-epson-erf',
-		fff: 'image/x-hasselblad-fff',
-		flv: 'video/x-flv',
-		gif: 'image/gif',
-		heic: 'image/heic',
-		heif: 'image/heif',
-		iiq: 'image/x-phaseone-iiq',
-		insp: 'image/jpeg',
-		insv: 'video/mp4',
-		jpeg: 'image/jpeg',
-		jpg: 'image/jpeg',
-		jxl: 'image/jxl',
-		k25: 'image/x-kodak-k25',
-		kdc: 'image/x-kodak-kdc',
-		m2ts: 'video/mp2t',
-		mkv: 'video/x-matroska',
-		mov: 'video/quicktime',
-		mp4: 'video/mp4',
-		mpg: 'video/mpeg',
-		mrw: 'image/x-minolta-mrw',
-		mts: 'video/mp2t',
-		nef: 'image/x-nikon-nef',
-		orf: 'image/x-olympus-orf',
-		ori: 'image/x-olympus-ori',
-		pef: 'image/x-pentax-pef',
-		png: 'image/png',
-		raf: 'image/x-fuji-raf',
-		raw: 'image/x-panasonic-raw',
-		rwl: 'image/x-leica-rwl',
-		sr2: 'image/x-sony-sr2',
-		srf: 'image/x-sony-srf',
-		srw: 'image/x-samsung-srw',
-		tiff: 'image/tiff',
-		webm: 'video/webm',
-		webp: 'image/webp',
-		wmv: 'video/x-ms-wmv',
-		x3f: 'image/x-sigma-x3f'
-	};
-	// Return the MIME type determined by the browser or the MIME type based on the file extension.
-	return file.type || (mimeTypes[getFilenameExtension(file.name)] ?? '');
+  const mimeTypes: Record<string, string> = {
+    '3fr': 'image/x-hasselblad-3fr',
+    '3gp': 'video/3gpp',
+    ari: 'image/x-arriflex-ari',
+    arw: 'image/x-sony-arw',
+    avi: 'video/avi',
+    avif: 'image/avif',
+    cap: 'image/x-phaseone-cap',
+    cin: 'image/x-phantom-cin',
+    cr2: 'image/x-canon-cr2',
+    cr3: 'image/x-canon-cr3',
+    crw: 'image/x-canon-crw',
+    dcr: 'image/x-kodak-dcr',
+    dng: 'image/x-adobe-dng',
+    erf: 'image/x-epson-erf',
+    fff: 'image/x-hasselblad-fff',
+    flv: 'video/x-flv',
+    gif: 'image/gif',
+    heic: 'image/heic',
+    heif: 'image/heif',
+    iiq: 'image/x-phaseone-iiq',
+    insp: 'image/jpeg',
+    insv: 'video/mp4',
+    jpeg: 'image/jpeg',
+    jpg: 'image/jpeg',
+    jxl: 'image/jxl',
+    k25: 'image/x-kodak-k25',
+    kdc: 'image/x-kodak-kdc',
+    m2ts: 'video/mp2t',
+    mkv: 'video/x-matroska',
+    mov: 'video/quicktime',
+    mp4: 'video/mp4',
+    mpg: 'video/mpeg',
+    mrw: 'image/x-minolta-mrw',
+    mts: 'video/mp2t',
+    nef: 'image/x-nikon-nef',
+    orf: 'image/x-olympus-orf',
+    ori: 'image/x-olympus-ori',
+    pef: 'image/x-pentax-pef',
+    png: 'image/png',
+    raf: 'image/x-fuji-raf',
+    raw: 'image/x-panasonic-raw',
+    rwl: 'image/x-leica-rwl',
+    sr2: 'image/x-sony-sr2',
+    srf: 'image/x-sony-srf',
+    srw: 'image/x-samsung-srw',
+    tiff: 'image/tiff',
+    webm: 'video/webm',
+    webp: 'image/webp',
+    wmv: 'video/x-ms-wmv',
+    x3f: 'image/x-sigma-x3f',
+  };
+  // Return the MIME type determined by the browser or the MIME type based on the file extension.
+  return file.type || (mimeTypes[getFilenameExtension(file.name)] ?? '');
 }
 
 function isRotated90CW(orientation: number) {
-	return orientation == 6 || orientation == 90;
+  return orientation == 6 || orientation == 90;
 }
 
 function isRotated270CW(orientation: number) {
-	return orientation == 8 || orientation == -90;
+  return orientation == 8 || orientation == -90;
 }
 
 /**
  * Returns aspect ratio for the asset
  */
 export function getAssetRatio(asset: AssetResponseDto) {
-	let height = asset.exifInfo?.exifImageHeight || 235;
-	let width = asset.exifInfo?.exifImageWidth || 235;
-	const orientation = Number(asset.exifInfo?.orientation);
-	if (orientation) {
-		if (isRotated90CW(orientation) || isRotated270CW(orientation)) {
-			[width, height] = [height, width];
-		}
-	}
-	return { width, height };
+  let height = asset.exifInfo?.exifImageHeight || 235;
+  let width = asset.exifInfo?.exifImageWidth || 235;
+  const orientation = Number(asset.exifInfo?.orientation);
+  if (orientation) {
+    if (isRotated90CW(orientation) || isRotated270CW(orientation)) {
+      [width, height] = [height, width];
+    }
+  }
+  return { width, height };
 }
diff --git a/web/src/lib/utils/byte-units.ts b/web/src/lib/utils/byte-units.ts
index 2ab620f5cd..4a41a23bc5 100644
--- a/web/src/lib/utils/byte-units.ts
+++ b/web/src/lib/utils/byte-units.ts
@@ -9,22 +9,22 @@
  * @returns size (number) and unit (string)
  */
 export function getBytesWithUnit(bytes: number, maxPrecision = 1): [number, string] {
-	const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
+  const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
 
-	let magnitude = 0;
-	let remainder = bytes;
-	while (remainder >= 1024) {
-		if (magnitude + 1 < units.length) {
-			magnitude++;
-			remainder /= 1024;
-		} else {
-			break;
-		}
-	}
+  let magnitude = 0;
+  let remainder = bytes;
+  while (remainder >= 1024) {
+    if (magnitude + 1 < units.length) {
+      magnitude++;
+      remainder /= 1024;
+    } else {
+      break;
+    }
+  }
 
-	remainder = parseFloat(remainder.toFixed(maxPrecision));
+  remainder = parseFloat(remainder.toFixed(maxPrecision));
 
-	return [remainder, units[magnitude]];
+  return [remainder, units[magnitude]];
 }
 
 /**
@@ -39,6 +39,6 @@ export function getBytesWithUnit(bytes: number, maxPrecision = 1): [number, stri
  * @returns localized bytes with unit as string
  */
 export function asByteUnitString(bytes: number, locale?: string, maxPrecision = 1): string {
-	const [size, unit] = getBytesWithUnit(bytes, maxPrecision);
-	return `${size.toLocaleString(locale)} ${unit}`;
+  const [size, unit] = getBytesWithUnit(bytes, maxPrecision);
+  return `${size.toLocaleString(locale)} ${unit}`;
 }
diff --git a/web/src/lib/utils/click-outside.ts b/web/src/lib/utils/click-outside.ts
index 3b73f085dd..7e929ab745 100644
--- a/web/src/lib/utils/click-outside.ts
+++ b/web/src/lib/utils/click-outside.ts
@@ -1,30 +1,30 @@
 import type { ActionReturn } from 'svelte/action';
 
 interface Attributes {
-	'on:outclick'?: (e: CustomEvent) => void;
+  'on:outclick'?: (e: CustomEvent) => void;
 }
 
 export function clickOutside(node: HTMLElement): ActionReturn<void, Attributes> {
-	const handleClick = (event: MouseEvent) => {
-		const targetNode = event.target as Node | null;
-		if (!node.contains(targetNode)) {
-			node.dispatchEvent(new CustomEvent('outclick'));
-		}
-	};
+  const handleClick = (event: MouseEvent) => {
+    const targetNode = event.target as Node | null;
+    if (!node.contains(targetNode)) {
+      node.dispatchEvent(new CustomEvent('outclick'));
+    }
+  };
 
-	const handleKey = (event: KeyboardEvent) => {
-		if (event.key === 'Escape') {
-			node.dispatchEvent(new CustomEvent('outclick'));
-		}
-	};
+  const handleKey = (event: KeyboardEvent) => {
+    if (event.key === 'Escape') {
+      node.dispatchEvent(new CustomEvent('outclick'));
+    }
+  };
 
-	document.addEventListener('click', handleClick, true);
-	document.addEventListener('keydown', handleKey, true);
+  document.addEventListener('click', handleClick, true);
+  document.addEventListener('keydown', handleKey, true);
 
-	return {
-		destroy() {
-			document.removeEventListener('click', handleClick, true);
-			document.removeEventListener('keydown', handleKey, true);
-		}
-	};
+  return {
+    destroy() {
+      document.removeEventListener('click', handleClick, true);
+      document.removeEventListener('keydown', handleKey, true);
+    },
+  };
 }
diff --git a/web/src/lib/utils/context.ts b/web/src/lib/utils/context.ts
index 05394097de..733b8e6eb9 100644
--- a/web/src/lib/utils/context.ts
+++ b/web/src/lib/utils/context.ts
@@ -1,8 +1,8 @@
 import { getContext, setContext } from 'svelte';
 
 export function createContext<T>(key: string | symbol = Symbol()) {
-	return {
-		get: () => getContext<T>(key),
-		set: (context: T) => setContext<T>(key, context)
-	};
+  return {
+    get: () => getContext<T>(key),
+    set: (context: T) => setContext<T>(key, context),
+  };
 }
diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts
index 908aec9708..2e5a411a26 100644
--- a/web/src/lib/utils/file-uploader.ts
+++ b/web/src/lib/utils/file-uploader.ts
@@ -4,196 +4,193 @@ import type { AssetFileUploadResponseDto } from '@api';
 import axios from 'axios';
 import { combineLatestAll, filter, firstValueFrom, from, mergeMap, of } from 'rxjs';
 import type { UploadAsset } from '../models/upload-asset';
-import {
-	notificationController,
-	NotificationType
-} from './../components/shared-components/notification/notification';
+import { notificationController, NotificationType } from './../components/shared-components/notification/notification';
 
 export const openFileUploadDialog = async (
-	albumId: string | undefined = undefined,
-	sharedKey: string | undefined = undefined
+  albumId: string | undefined = undefined,
+  sharedKey: string | undefined = undefined,
 ) => {
-	return new Promise<(string | undefined)[]>((resolve, reject) => {
-		try {
-			const fileSelector = document.createElement('input');
+  return new Promise<(string | undefined)[]>((resolve, reject) => {
+    try {
+      const fileSelector = document.createElement('input');
 
-			fileSelector.type = 'file';
-			fileSelector.multiple = true;
+      fileSelector.type = 'file';
+      fileSelector.multiple = true;
 
-			// When adding a content type that is unsupported by browsers, make sure
-			// to also add it to getFileMimeType() otherwise the upload will fail.
-			fileSelector.accept = [
-				'image/*',
-				'video/*',
-				'.3fr',
-				'.3gp',
-				'.ari',
-				'.arw',
-				'.avif',
-				'.cap',
-				'.cin',
-				'.cr2',
-				'.cr3',
-				'.crw',
-				'.dcr',
-				'.dng',
-				'.erf',
-				'.fff',
-				'.heic',
-				'.heif',
-				'.iiq',
-				'.insp',
-				'.insv',
-				'.jxl',
-				'.k25',
-				'.kdc',
-				'.m2ts',
-				'.mov',
-				'.mrw',
-				'.mts',
-				'.nef',
-				'.orf',
-				'.ori',
-				'.pef',
-				'.raf',
-				'.raf',
-				'.raw',
-				'.rwl',
-				'.sr2',
-				'.srf',
-				'.srw',
-				'.x3f'
-			].join(',');
+      // When adding a content type that is unsupported by browsers, make sure
+      // to also add it to getFileMimeType() otherwise the upload will fail.
+      fileSelector.accept = [
+        'image/*',
+        'video/*',
+        '.3fr',
+        '.3gp',
+        '.ari',
+        '.arw',
+        '.avif',
+        '.cap',
+        '.cin',
+        '.cr2',
+        '.cr3',
+        '.crw',
+        '.dcr',
+        '.dng',
+        '.erf',
+        '.fff',
+        '.heic',
+        '.heif',
+        '.iiq',
+        '.insp',
+        '.insv',
+        '.jxl',
+        '.k25',
+        '.kdc',
+        '.m2ts',
+        '.mov',
+        '.mrw',
+        '.mts',
+        '.nef',
+        '.orf',
+        '.ori',
+        '.pef',
+        '.raf',
+        '.raf',
+        '.raw',
+        '.rwl',
+        '.sr2',
+        '.srf',
+        '.srw',
+        '.x3f',
+      ].join(',');
 
-			fileSelector.onchange = async (e: Event) => {
-				const target = e.target as HTMLInputElement;
-				if (!target.files) {
-					return;
-				}
-				const files = Array.from<File>(target.files);
+      fileSelector.onchange = async (e: Event) => {
+        const target = e.target as HTMLInputElement;
+        if (!target.files) {
+          return;
+        }
+        const files = Array.from<File>(target.files);
 
-				resolve(await fileUploadHandler(files, albumId, sharedKey));
-			};
+        resolve(await fileUploadHandler(files, albumId, sharedKey));
+      };
 
-			fileSelector.click();
-		} catch (e) {
-			console.log('Error selecting file', e);
-			reject(e);
-		}
-	});
+      fileSelector.click();
+    } catch (e) {
+      console.log('Error selecting file', e);
+      reject(e);
+    }
+  });
 };
 
 export const fileUploadHandler = async (
-	files: File[],
-	albumId: string | undefined = undefined,
-	sharedKey: string | undefined = undefined
+  files: File[],
+  albumId: string | undefined = undefined,
+  sharedKey: string | undefined = undefined,
 ) => {
-	return firstValueFrom(
-		from(files).pipe(
-			filter((file) => {
-				const assetType = getFileMimeType(file).split('/')[0];
-				return assetType === 'video' || assetType === 'image';
-			}),
-			mergeMap(async (file) => of(await fileUploader(file, albumId, sharedKey)), 2),
-			combineLatestAll()
-		)
-	);
+  return firstValueFrom(
+    from(files).pipe(
+      filter((file) => {
+        const assetType = getFileMimeType(file).split('/')[0];
+        return assetType === 'video' || assetType === 'image';
+      }),
+      mergeMap(async (file) => of(await fileUploader(file, albumId, sharedKey)), 2),
+      combineLatestAll(),
+    ),
+  );
 };
 
 //TODO: should probably use the @api SDK
 async function fileUploader(
-	asset: File,
-	albumId: string | undefined = undefined,
-	sharedKey: string | undefined = undefined
+  asset: File,
+  albumId: string | undefined = undefined,
+  sharedKey: string | undefined = undefined,
 ): Promise<string | undefined> {
-	const mimeType = getFileMimeType(asset);
-	const assetType = mimeType.split('/')[0].toUpperCase();
-	const fileExtension = getFilenameExtension(asset.name);
-	const formData = new FormData();
-	const fileCreatedAt = new Date(asset.lastModified).toISOString();
-	const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified;
+  const mimeType = getFileMimeType(asset);
+  const assetType = mimeType.split('/')[0].toUpperCase();
+  const fileExtension = getFilenameExtension(asset.name);
+  const formData = new FormData();
+  const fileCreatedAt = new Date(asset.lastModified).toISOString();
+  const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified;
 
-	try {
-		// Create and add pseudo-unique ID of asset on the device
-		formData.append('deviceAssetId', deviceAssetId);
+  try {
+    // Create and add pseudo-unique ID of asset on the device
+    formData.append('deviceAssetId', deviceAssetId);
 
-		// Get device id - for web -> use WEB
-		formData.append('deviceId', 'WEB');
+    // Get device id - for web -> use WEB
+    formData.append('deviceId', 'WEB');
 
-		// Get asset type
-		formData.append('assetType', assetType);
+    // Get asset type
+    formData.append('assetType', assetType);
 
-		// Get Asset Created Date
-		formData.append('fileCreatedAt', fileCreatedAt);
+    // Get Asset Created Date
+    formData.append('fileCreatedAt', fileCreatedAt);
 
-		// Get Asset Modified At
-		formData.append('fileModifiedAt', new Date(asset.lastModified).toISOString());
+    // Get Asset Modified At
+    formData.append('fileModifiedAt', new Date(asset.lastModified).toISOString());
 
-		// Set Asset is Favorite to false
-		formData.append('isFavorite', 'false');
+    // Set Asset is Favorite to false
+    formData.append('isFavorite', 'false');
 
-		// Get asset duration
-		formData.append('duration', '0:00:00.000000');
+    // Get asset duration
+    formData.append('duration', '0:00:00.000000');
 
-		// Get asset file extension
-		formData.append('fileExtension', '.' + fileExtension);
+    // Get asset file extension
+    formData.append('fileExtension', '.' + fileExtension);
 
-		// Get asset binary data with a custom MIME type, because browsers will
-		// use application/octet-stream for unsupported MIME types, leading to
-		// failed uploads.
-		formData.append('assetData', new File([asset], asset.name, { type: mimeType }));
+    // Get asset binary data with a custom MIME type, because browsers will
+    // use application/octet-stream for unsupported MIME types, leading to
+    // failed uploads.
+    formData.append('assetData', new File([asset], asset.name, { type: mimeType }));
 
-		const newUploadAsset: UploadAsset = {
-			id: deviceAssetId,
-			file: asset,
-			progress: 0,
-			fileExtension: fileExtension
-		};
+    const newUploadAsset: UploadAsset = {
+      id: deviceAssetId,
+      file: asset,
+      progress: 0,
+      fileExtension: fileExtension,
+    };
 
-		uploadAssetsStore.addNewUploadAsset(newUploadAsset);
+    uploadAssetsStore.addNewUploadAsset(newUploadAsset);
 
-		const response = await axios.post(`/api/asset/upload`, formData, {
-			params: {
-				key: sharedKey
-			},
-			onUploadProgress: (event) => {
-				const percentComplete = Math.floor((event.loaded / event.total) * 100);
-				uploadAssetsStore.updateProgress(deviceAssetId, percentComplete);
-			}
-		});
+    const response = await axios.post(`/api/asset/upload`, formData, {
+      params: {
+        key: sharedKey,
+      },
+      onUploadProgress: (event) => {
+        const percentComplete = Math.floor((event.loaded / event.total) * 100);
+        uploadAssetsStore.updateProgress(deviceAssetId, percentComplete);
+      },
+    });
 
-		if (response.status == 200 || response.status == 201) {
-			const res: AssetFileUploadResponseDto = response.data;
+    if (response.status == 200 || response.status == 201) {
+      const res: AssetFileUploadResponseDto = response.data;
 
-			if (albumId && res.id) {
-				await addAssetsToAlbum(albumId, [res.id], sharedKey);
-			}
+      if (albumId && res.id) {
+        await addAssetsToAlbum(albumId, [res.id], sharedKey);
+      }
 
-			setTimeout(() => {
-				uploadAssetsStore.removeUploadAsset(deviceAssetId);
-			}, 1000);
+      setTimeout(() => {
+        uploadAssetsStore.removeUploadAsset(deviceAssetId);
+      }, 1000);
 
-			return res.id;
-		}
-	} catch (e) {
-		console.log('error uploading file ', e);
-		handleUploadError(asset, JSON.stringify(e));
-		uploadAssetsStore.removeUploadAsset(deviceAssetId);
-	}
+      return res.id;
+    }
+  } catch (e) {
+    console.log('error uploading file ', e);
+    handleUploadError(asset, JSON.stringify(e));
+    uploadAssetsStore.removeUploadAsset(deviceAssetId);
+  }
 }
 
 function handleUploadError(asset: File, respBody = '{}', extraMessage?: string) {
-	try {
-		const res = JSON.parse(respBody);
+  try {
+    const res = JSON.parse(respBody);
 
-		const extraMsg = res ? ' ' + res?.message : '';
+    const extraMsg = res ? ' ' + res?.message : '';
 
-		notificationController.show({
-			type: NotificationType.Error,
-			message: `Cannot upload file ${asset.name} ${extraMsg}${extraMessage}`,
-			timeout: 5000
-		});
-	} catch (e) {
-		console.error('ERROR parsing data JSON in handleUploadError');
-	}
+    notificationController.show({
+      type: NotificationType.Error,
+      message: `Cannot upload file ${asset.name} ${extraMsg}${extraMessage}`,
+      timeout: 5000,
+    });
+  } catch (e) {
+    console.error('ERROR parsing data JSON in handleUploadError');
+  }
 }
diff --git a/web/src/lib/utils/get-github-version.ts b/web/src/lib/utils/get-github-version.ts
index e3ae98190e..5748585050 100644
--- a/web/src/lib/utils/get-github-version.ts
+++ b/web/src/lib/utils/get-github-version.ts
@@ -1,18 +1,15 @@
 import axios from 'axios';
 
 type GithubRelease = {
-	tag_name: string;
+  tag_name: string;
 };
 
 export const getGithubVersion = async (): Promise<string> => {
-	const { data } = await axios.get<GithubRelease>(
-		'https://api.github.com/repos/immich-app/immich/releases/latest',
-		{
-			headers: {
-				Accept: 'application/vnd.github.v3+json'
-			}
-		}
-	);
+  const { data } = await axios.get<GithubRelease>('https://api.github.com/repos/immich-app/immich/releases/latest', {
+    headers: {
+      Accept: 'application/vnd.github.v3+json',
+    },
+  });
 
-	return data.tag_name;
+  return data.tag_name;
 };
diff --git a/web/src/lib/utils/handle-error.ts b/web/src/lib/utils/handle-error.ts
index bb3ad69307..3c9c0e1c93 100644
--- a/web/src/lib/utils/handle-error.ts
+++ b/web/src/lib/utils/handle-error.ts
@@ -1,29 +1,26 @@
 import type { ApiError } from '@api';
-import {
-	notificationController,
-	NotificationType
-} from '../components/shared-components/notification/notification';
+import { notificationController, NotificationType } from '../components/shared-components/notification/notification';
 
 export async function handleError(error: unknown, message: string) {
-	console.error(`[handleError]: ${message}`, error);
+  console.error(`[handleError]: ${message}`, error);
 
-	let data = (error as ApiError)?.response?.data;
-	if (data instanceof Blob) {
-		const response = await data.text();
-		try {
-			data = JSON.parse(response);
-		} catch {
-			data = { message: response };
-		}
-	}
+  let data = (error as ApiError)?.response?.data;
+  if (data instanceof Blob) {
+    const response = await data.text();
+    try {
+      data = JSON.parse(response);
+    } catch {
+      data = { message: response };
+    }
+  }
 
-	let serverMessage = data?.message;
-	if (serverMessage) {
-		serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`;
-	}
+  let serverMessage = data?.message;
+  if (serverMessage) {
+    serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`;
+  }
 
-	notificationController.show({
-		message: serverMessage || message,
-		type: NotificationType.Error
-	});
+  notificationController.show({
+    message: serverMessage || message,
+    type: NotificationType.Error,
+  });
 }
diff --git a/web/src/lib/utils/image-load.ts b/web/src/lib/utils/image-load.ts
index a3a7858713..d8e35223b9 100644
--- a/web/src/lib/utils/image-load.ts
+++ b/web/src/lib/utils/image-load.ts
@@ -2,37 +2,37 @@ import { tick } from 'svelte';
 import type { ActionReturn } from 'svelte/action';
 
 interface Attributes {
-	'on:image-error'?: (e: CustomEvent) => void;
-	'on:image-load'?: (e: CustomEvent) => void;
+  'on:image-error'?: (e: CustomEvent) => void;
+  'on:image-load'?: (e: CustomEvent) => void;
 }
 
 export function imageLoad(img: HTMLImageElement): ActionReturn<void, Attributes> {
-	const onImageError = () => img.dispatchEvent(new CustomEvent('image-error'));
-	const onImageLoaded = () => img.dispatchEvent(new CustomEvent('image-load'));
+  const onImageError = () => img.dispatchEvent(new CustomEvent('image-error'));
+  const onImageLoaded = () => img.dispatchEvent(new CustomEvent('image-load'));
 
-	if (img.complete) {
-		// Browser has fetched the image, naturalHeight is used to check
-		// if any loading errors have occurred.
-		const loadingError = img.naturalHeight === 0;
+  if (img.complete) {
+    // Browser has fetched the image, naturalHeight is used to check
+    // if any loading errors have occurred.
+    const loadingError = img.naturalHeight === 0;
 
-		// Report status after a tick, to make sure event listeners are registered.
-		if (loadingError) {
-			tick().then(onImageError);
-		} else {
-			tick().then(onImageLoaded);
-		}
+    // Report status after a tick, to make sure event listeners are registered.
+    if (loadingError) {
+      tick().then(onImageError);
+    } else {
+      tick().then(onImageLoaded);
+    }
 
-		return {};
-	}
+    return {};
+  }
 
-	// Image has not been loaded yet, report status with event listeners.
-	img.addEventListener('load', onImageLoaded, { once: true });
-	img.addEventListener('error', onImageError, { once: true });
+  // Image has not been loaded yet, report status with event listeners.
+  img.addEventListener('load', onImageLoaded, { once: true });
+  img.addEventListener('error', onImageError, { once: true });
 
-	return {
-		destroy() {
-			img.removeEventListener('load', onImageLoaded);
-			img.removeEventListener('error', onImageError);
-		}
-	};
+  return {
+    destroy() {
+      img.removeEventListener('load', onImageLoaded);
+      img.removeEventListener('error', onImageError);
+    },
+  };
 }
diff --git a/web/src/lib/utils/time-to-seconds.spec.ts b/web/src/lib/utils/time-to-seconds.spec.ts
index 5cc644417e..0bb4328572 100644
--- a/web/src/lib/utils/time-to-seconds.spec.ts
+++ b/web/src/lib/utils/time-to-seconds.spec.ts
@@ -1,24 +1,24 @@
-import { describe, it, expect } from '@jest/globals';
+import { describe, expect, it } from '@jest/globals';
 import { timeToSeconds } from './time-to-seconds';
 
 describe('converting time to seconds', () => {
-	it('parses hh:mm:ss correctly', () => {
-		expect(timeToSeconds('01:02:03')).toBeCloseTo(3723);
-	});
+  it('parses hh:mm:ss correctly', () => {
+    expect(timeToSeconds('01:02:03')).toBeCloseTo(3723);
+  });
 
-	it('parses hh:mm:ss.SSS correctly', () => {
-		expect(timeToSeconds('01:02:03.456')).toBeCloseTo(3723.456);
-	});
+  it('parses hh:mm:ss.SSS correctly', () => {
+    expect(timeToSeconds('01:02:03.456')).toBeCloseTo(3723.456);
+  });
 
-	it('parses h:m:s.S correctly', () => {
-		expect(timeToSeconds('1:2:3.4')).toBeCloseTo(3723.4);
-	});
+  it('parses h:m:s.S correctly', () => {
+    expect(timeToSeconds('1:2:3.4')).toBeCloseTo(3723.4);
+  });
 
-	it('parses hhh:mm:ss.SSS correctly', () => {
-		expect(timeToSeconds('100:02:03.456')).toBeCloseTo(360123.456);
-	});
+  it('parses hhh:mm:ss.SSS correctly', () => {
+    expect(timeToSeconds('100:02:03.456')).toBeCloseTo(360123.456);
+  });
 
-	it('ignores ignores double milliseconds hh:mm:ss.SSS.SSSSSS', () => {
-		expect(timeToSeconds('01:02:03.456.123456')).toBeCloseTo(3723.456);
-	});
+  it('ignores ignores double milliseconds hh:mm:ss.SSS.SSSSSS', () => {
+    expect(timeToSeconds('01:02:03.456.123456')).toBeCloseTo(3723.456);
+  });
 });
diff --git a/web/src/lib/utils/time-to-seconds.ts b/web/src/lib/utils/time-to-seconds.ts
index 4b710fd53b..026615ea03 100644
--- a/web/src/lib/utils/time-to-seconds.ts
+++ b/web/src/lib/utils/time-to-seconds.ts
@@ -4,10 +4,10 @@ import { Duration } from 'luxon';
  * Convert time like `01:02:03.456` to seconds.
  */
 export function timeToSeconds(time: string) {
-	const parts = time.split(':');
-	parts[2] = parts[2].split('.').slice(0, 2).join('.');
+  const parts = time.split(':');
+  parts[2] = parts[2].split('.').slice(0, 2).join('.');
 
-	const [hours, minutes, seconds] = parts.map(Number);
+  const [hours, minutes, seconds] = parts.map(Number);
 
-	return Duration.fromObject({ hours, minutes, seconds }).as('seconds');
+  return Duration.fromObject({ hours, minutes, seconds }).as('seconds');
 }
diff --git a/web/src/routes/(user)/albums/+page.server.ts b/web/src/routes/(user)/albums/+page.server.ts
index d138401487..a9373d09c5 100644
--- a/web/src/routes/(user)/albums/+page.server.ts
+++ b/web/src/routes/(user)/albums/+page.server.ts
@@ -3,21 +3,21 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { api, user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	try {
-		const { data: albums } = await api.albumApi.getAllAlbums();
+  try {
+    const { data: albums } = await api.albumApi.getAllAlbums();
 
-		return {
-			user: user,
-			albums: albums,
-			meta: {
-				title: 'Albums'
-			}
-		};
-	} catch (e) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+    return {
+      user: user,
+      albums: albums,
+      meta: {
+        title: 'Albums',
+      },
+    };
+  } catch (e) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/albums/+page.svelte b/web/src/routes/(user)/albums/+page.svelte
index 59f57f6137..9624da0b2f 100644
--- a/web/src/routes/(user)/albums/+page.svelte
+++ b/web/src/routes/(user)/albums/+page.svelte
@@ -1,171 +1,163 @@
 <script lang="ts">
-	import { albumViewSettings } from '$lib/stores/preferences.store';
-	import AlbumCard from '$lib/components/album-page/album-card.svelte';
-	import { goto } from '$app/navigation';
-	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
-	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import type { PageData } from './$types';
-	import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
-	import { useAlbums } from './albums.bloc';
-	import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
-	import { onMount } from 'svelte';
-	import { flip } from 'svelte/animate';
-	import Dropdown from '$lib/components/elements/dropdown.svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import type { AlbumResponseDto } from '@api';
+  import { albumViewSettings } from '$lib/stores/preferences.store';
+  import AlbumCard from '$lib/components/album-page/album-card.svelte';
+  import { goto } from '$app/navigation';
+  import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
+  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+  import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+  import type { PageData } from './$types';
+  import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
+  import { useAlbums } from './albums.bloc';
+  import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
+  import { onMount } from 'svelte';
+  import { flip } from 'svelte/animate';
+  import Dropdown from '$lib/components/elements/dropdown.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import type { AlbumResponseDto } from '@api';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	const sortByOptions = ['Most recent photo', 'Last modified', 'Album title'];
+  const sortByOptions = ['Most recent photo', 'Last modified', 'Album title'];
 
-	const {
-		albums: unsortedAlbums,
-		isShowContextMenu,
-		contextMenuPosition,
-		contextMenuTargetAlbum,
-		createAlbum,
-		deleteAlbum,
-		showAlbumContextMenu,
-		closeAlbumContextMenu
-	} = useAlbums({ albums: data.albums });
+  const {
+    albums: unsortedAlbums,
+    isShowContextMenu,
+    contextMenuPosition,
+    contextMenuTargetAlbum,
+    createAlbum,
+    deleteAlbum,
+    showAlbumContextMenu,
+    closeAlbumContextMenu,
+  } = useAlbums({ albums: data.albums });
 
-	let albums = unsortedAlbums;
-	let albumToDelete: AlbumResponseDto | null;
+  let albums = unsortedAlbums;
+  let albumToDelete: AlbumResponseDto | null;
 
-	const setAlbumToDelete = () => {
-		albumToDelete = $contextMenuTargetAlbum ?? null;
-		closeAlbumContextMenu();
-	};
+  const setAlbumToDelete = () => {
+    albumToDelete = $contextMenuTargetAlbum ?? null;
+    closeAlbumContextMenu();
+  };
 
-	const deleteSelectedAlbum = async () => {
-		if (!albumToDelete) {
-			return;
-		}
-		try {
-			await deleteAlbum(albumToDelete);
-		} catch {
-			notificationController.show({
-				message: 'Error deleting album',
-				type: NotificationType.Error
-			});
-		} finally {
-			albumToDelete = null;
-		}
-	};
+  const deleteSelectedAlbum = async () => {
+    if (!albumToDelete) {
+      return;
+    }
+    try {
+      await deleteAlbum(albumToDelete);
+    } catch {
+      notificationController.show({
+        message: 'Error deleting album',
+        type: NotificationType.Error,
+      });
+    } finally {
+      albumToDelete = null;
+    }
+  };
 
-	const sortByDate = (a: string, b: string) => {
-		const aDate = new Date(a);
-		const bDate = new Date(b);
-		return bDate.getTime() - aDate.getTime();
-	};
+  const sortByDate = (a: string, b: string) => {
+    const aDate = new Date(a);
+    const bDate = new Date(b);
+    return bDate.getTime() - aDate.getTime();
+  };
 
-	$: {
-		const { sortBy } = $albumViewSettings;
-		if (sortBy === 'Most recent photo') {
-			$albums = $unsortedAlbums.sort((a, b) =>
-				a.lastModifiedAssetTimestamp && b.lastModifiedAssetTimestamp
-					? sortByDate(a.lastModifiedAssetTimestamp, b.lastModifiedAssetTimestamp)
-					: sortByDate(a.updatedAt, b.updatedAt)
-			);
-		} else if (sortBy === 'Last modified') {
-			$albums = $unsortedAlbums.sort((a, b) => sortByDate(a.updatedAt, b.updatedAt));
-		} else if (sortBy === 'Album title') {
-			$albums = $unsortedAlbums.sort((a, b) => a.albumName.localeCompare(b.albumName));
-		}
-	}
+  $: {
+    const { sortBy } = $albumViewSettings;
+    if (sortBy === 'Most recent photo') {
+      $albums = $unsortedAlbums.sort((a, b) =>
+        a.lastModifiedAssetTimestamp && b.lastModifiedAssetTimestamp
+          ? sortByDate(a.lastModifiedAssetTimestamp, b.lastModifiedAssetTimestamp)
+          : sortByDate(a.updatedAt, b.updatedAt),
+      );
+    } else if (sortBy === 'Last modified') {
+      $albums = $unsortedAlbums.sort((a, b) => sortByDate(a.updatedAt, b.updatedAt));
+    } else if (sortBy === 'Album title') {
+      $albums = $unsortedAlbums.sort((a, b) => a.albumName.localeCompare(b.albumName));
+    }
+  }
 
-	const handleCreateAlbum = async () => {
-		const newAlbum = await createAlbum();
-		if (newAlbum) {
-			goto('/albums/' + newAlbum.id);
-		}
-	};
+  const handleCreateAlbum = async () => {
+    const newAlbum = await createAlbum();
+    if (newAlbum) {
+      goto('/albums/' + newAlbum.id);
+    }
+  };
 
-	onMount(() => {
-		removeAlbumsIfEmpty();
-	});
+  onMount(() => {
+    removeAlbumsIfEmpty();
+  });
 
-	const removeAlbumsIfEmpty = async () => {
-		try {
-			for (const album of $albums) {
-				if (album.assetCount == 0 && album.albumName == 'Untitled') {
-					await deleteAlbum(album);
-				}
-			}
-		} catch (error) {
-			console.log(error);
-		}
-	};
+  const removeAlbumsIfEmpty = async () => {
+    try {
+      for (const album of $albums) {
+        if (album.assetCount == 0 && album.albumName == 'Untitled') {
+          await deleteAlbum(album);
+        }
+      }
+    } catch (error) {
+      console.log(error);
+    }
+  };
 </script>
 
 <UserPageLayout user={data.user} title={data.meta.title}>
-	<div class="flex place-items-center gap-2" slot="buttons">
-		<LinkButton on:click={handleCreateAlbum}>
-			<div class="flex place-items-center gap-2 text-sm">
-				<PlusBoxOutline size="18" />
-				Create album
-			</div>
-		</LinkButton>
+  <div class="flex place-items-center gap-2" slot="buttons">
+    <LinkButton on:click={handleCreateAlbum}>
+      <div class="flex place-items-center gap-2 text-sm">
+        <PlusBoxOutline size="18" />
+        Create album
+      </div>
+    </LinkButton>
 
-		<Dropdown options={sortByOptions} bind:value={$albumViewSettings.sortBy} />
-	</div>
+    <Dropdown options={sortByOptions} bind:value={$albumViewSettings.sortBy} />
+  </div>
 
-	<!-- Album Card -->
-	<div class="grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]">
-		{#each $albums as album (album.id)}
-			<a
-				data-sveltekit-preload-data="hover"
-				href={`albums/${album.id}`}
-				animate:flip={{ duration: 200 }}
-			>
-				<AlbumCard
-					{album}
-					on:showalbumcontextmenu={(e) => showAlbumContextMenu(e.detail, album)}
-					user={data.user}
-				/>
-			</a>
-		{/each}
-	</div>
+  <!-- Album Card -->
+  <div class="grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]">
+    {#each $albums as album (album.id)}
+      <a data-sveltekit-preload-data="hover" href={`albums/${album.id}`} animate:flip={{ duration: 200 }}>
+        <AlbumCard {album} on:showalbumcontextmenu={(e) => showAlbumContextMenu(e.detail, album)} user={data.user} />
+      </a>
+    {/each}
+  </div>
 
-	<!-- Empty Message -->
-	{#if $albums.length === 0}
-		<EmptyPlaceholder
-			text="Create an album to organize your photos and videos"
-			actionHandler={handleCreateAlbum}
-			alt="Empty albums"
-		/>
-	{/if}
+  <!-- Empty Message -->
+  {#if $albums.length === 0}
+    <EmptyPlaceholder
+      text="Create an album to organize your photos and videos"
+      actionHandler={handleCreateAlbum}
+      alt="Empty albums"
+    />
+  {/if}
 </UserPageLayout>
 
 <!-- Context Menu -->
 {#if $isShowContextMenu}
-	<ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}>
-		<MenuOption on:click={() => setAlbumToDelete()}>
-			<span class="flex place-items-center place-content-center gap-2">
-				<DeleteOutline size="18" />
-				<p>Delete album</p>
-			</span>
-		</MenuOption>
-	</ContextMenu>
+  <ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}>
+    <MenuOption on:click={() => setAlbumToDelete()}>
+      <span class="flex place-items-center place-content-center gap-2">
+        <DeleteOutline size="18" />
+        <p>Delete album</p>
+      </span>
+    </MenuOption>
+  </ContextMenu>
 {/if}
 
 {#if albumToDelete}
-	<ConfirmDialogue
-		title="Delete Album"
-		confirmText="Delete"
-		on:confirm={deleteSelectedAlbum}
-		on:cancel={() => (albumToDelete = null)}
-	>
-		<svelte:fragment slot="prompt">
-			<p>Are you sure you want to delete the album <b>{albumToDelete.albumName}</b>?</p>
-			<p>If this album is shared, other users will not be able to access it anymore.</p>
-		</svelte:fragment>
-	</ConfirmDialogue>
+  <ConfirmDialogue
+    title="Delete Album"
+    confirmText="Delete"
+    on:confirm={deleteSelectedAlbum}
+    on:cancel={() => (albumToDelete = null)}
+  >
+    <svelte:fragment slot="prompt">
+      <p>Are you sure you want to delete the album <b>{albumToDelete.albumName}</b>?</p>
+      <p>If this album is shared, other users will not be able to access it anymore.</p>
+    </svelte:fragment>
+  </ConfirmDialogue>
 {/if}
diff --git a/web/src/routes/(user)/albums/[albumId]/+page.server.ts b/web/src/routes/(user)/albums/[albumId]/+page.server.ts
index 75764423ef..bb01e29285 100644
--- a/web/src/routes/(user)/albums/[albumId]/+page.server.ts
+++ b/web/src/routes/(user)/albums/[albumId]/+page.server.ts
@@ -3,21 +3,21 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ params, locals: { api, user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const albumId = params['albumId'];
+  const albumId = params['albumId'];
 
-	try {
-		const { data: album } = await api.albumApi.getAlbumInfo({ id: albumId });
-		return {
-			album,
-			meta: {
-				title: album.albumName
-			}
-		};
-	} catch (e) {
-		throw redirect(302, AppRoute.ALBUMS);
-	}
+  try {
+    const { data: album } = await api.albumApi.getAlbumInfo({ id: albumId });
+    return {
+      album,
+      meta: {
+        title: album.albumName,
+      },
+    };
+  } catch (e) {
+    throw redirect(302, AppRoute.ALBUMS);
+  }
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte
index 2b28a37e89..ecb749b4ba 100644
--- a/web/src/routes/(user)/albums/[albumId]/+page.svelte
+++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte
@@ -1,10 +1,10 @@
 <script lang="ts">
-	import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
-	import type { PageData } from './$types';
+  import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 </script>
 
 <div class="immich-scrollbar">
-	<AlbumViewer album={data.album} />
+  <AlbumViewer album={data.album} />
 </div>
diff --git a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts
index 683d8492de..cb325776ee 100644
--- a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts
+++ b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts
@@ -1,19 +1,19 @@
-import { redirect } from '@sveltejs/kit';
-export const prerender = false;
-import type { PageLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageLoad } from './$types';
+export const prerender = false;
 
 export const load: PageLoad = async ({ params, parent }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const albumId = params['albumId'];
+  const albumId = params['albumId'];
 
-	if (albumId) {
-		throw redirect(302, `${AppRoute.ALBUMS}/${albumId}`);
-	} else {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (albumId) {
+    throw redirect(302, `${AppRoute.ALBUMS}/${albumId}`);
+  } else {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 };
diff --git a/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts b/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts
index 0ae8afe5c4..660c3f195a 100644
--- a/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts
+++ b/web/src/routes/(user)/albums/__tests__/albums.bloc.spec.ts
@@ -1,142 +1,137 @@
-import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
-import { useAlbums } from '../albums.bloc';
+import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
 import { api, CreateAlbumDto } from '@api';
-import {
-	notificationController,
-	NotificationType
-} from '$lib/components/shared-components/notification/notification';
+import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals';
 import { albumFactory } from '@test-data';
 import { get } from 'svelte/store';
+import { useAlbums } from '../albums.bloc';
 
 jest.mock('@api');
 
 const apiMock: jest.MockedObject<typeof api> = api as jest.MockedObject<typeof api>;
 
 describe('Albums BLoC', () => {
-	let sut: ReturnType<typeof useAlbums>;
-	const _albums = albumFactory.buildList(5);
+  let sut: ReturnType<typeof useAlbums>;
+  const _albums = albumFactory.buildList(5);
 
-	beforeEach(() => {
-		sut = useAlbums({ albums: [..._albums] });
-	});
+  beforeEach(() => {
+    sut = useAlbums({ albums: [..._albums] });
+  });
 
-	afterEach(() => {
-		const notifications = get(notificationController.notificationList);
+  afterEach(() => {
+    const notifications = get(notificationController.notificationList);
 
-		notifications.forEach((notification) =>
-			notificationController.removeNotificationById(notification.id)
-		);
-	});
+    notifications.forEach((notification) => notificationController.removeNotificationById(notification.id));
+  });
 
-	it('inits with provided albums', () => {
-		const albums = get(sut.albums);
-		expect(albums.length).toEqual(5);
-		expect(albums).toEqual(_albums);
-	});
+  it('inits with provided albums', () => {
+    const albums = get(sut.albums);
+    expect(albums.length).toEqual(5);
+    expect(albums).toEqual(_albums);
+  });
 
-	it('loads albums from the server', async () => {
-		// TODO: this method currently deletes albums with no assets and albumName === 'Untitled' which might not be the best approach
-		const loadedAlbums = [..._albums, albumFactory.build({ id: 'new_loaded_uuid' })];
+  it('loads albums from the server', async () => {
+    // TODO: this method currently deletes albums with no assets and albumName === 'Untitled' which might not be the best approach
+    const loadedAlbums = [..._albums, albumFactory.build({ id: 'new_loaded_uuid' })];
 
-		apiMock.albumApi.getAllAlbums.mockResolvedValueOnce({
-			data: loadedAlbums,
-			config: {},
-			headers: {},
-			status: 200,
-			statusText: ''
-		});
+    apiMock.albumApi.getAllAlbums.mockResolvedValueOnce({
+      data: loadedAlbums,
+      config: {},
+      headers: {},
+      status: 200,
+      statusText: '',
+    });
 
-		await sut.loadAlbums();
-		const albums = get(sut.albums);
+    await sut.loadAlbums();
+    const albums = get(sut.albums);
 
-		expect(apiMock.albumApi.getAllAlbums).toHaveBeenCalledTimes(1);
-		expect(albums).toEqual(loadedAlbums);
-	});
+    expect(apiMock.albumApi.getAllAlbums).toHaveBeenCalledTimes(1);
+    expect(albums).toEqual(loadedAlbums);
+  });
 
-	it('shows error message when it fails loading albums', async () => {
-		apiMock.albumApi.getAllAlbums.mockRejectedValueOnce({}); // TODO: implement APIProblem interface in the server
+  it('shows error message when it fails loading albums', async () => {
+    apiMock.albumApi.getAllAlbums.mockRejectedValueOnce({}); // TODO: implement APIProblem interface in the server
 
-		expect(get(notificationController.notificationList)).toHaveLength(0);
-		await sut.loadAlbums();
-		const albums = get(sut.albums);
-		const notifications = get(notificationController.notificationList);
+    expect(get(notificationController.notificationList)).toHaveLength(0);
+    await sut.loadAlbums();
+    const albums = get(sut.albums);
+    const notifications = get(notificationController.notificationList);
 
-		expect(apiMock.albumApi.getAllAlbums).toHaveBeenCalledTimes(1);
-		expect(albums).toEqual(_albums);
-		expect(notifications).toHaveLength(1);
-		expect(notifications[0].type).toEqual(NotificationType.Error);
-	});
+    expect(apiMock.albumApi.getAllAlbums).toHaveBeenCalledTimes(1);
+    expect(albums).toEqual(_albums);
+    expect(notifications).toHaveLength(1);
+    expect(notifications[0].type).toEqual(NotificationType.Error);
+  });
 
-	it('creates a new album', async () => {
-		// TODO: we probably shouldn't hardcode the album name "untitled" here and let the user input the album name before creating it
-		const payload: CreateAlbumDto = {
-			albumName: 'Untitled'
-		};
+  it('creates a new album', async () => {
+    // TODO: we probably shouldn't hardcode the album name "untitled" here and let the user input the album name before creating it
+    const payload: CreateAlbumDto = {
+      albumName: 'Untitled',
+    };
 
-		const returnedAlbum = albumFactory.build();
+    const returnedAlbum = albumFactory.build();
 
-		apiMock.albumApi.createAlbum.mockResolvedValueOnce({
-			data: returnedAlbum,
-			config: {},
-			headers: {},
-			status: 200,
-			statusText: ''
-		});
+    apiMock.albumApi.createAlbum.mockResolvedValueOnce({
+      data: returnedAlbum,
+      config: {},
+      headers: {},
+      status: 200,
+      statusText: '',
+    });
 
-		const newAlbum = await sut.createAlbum();
+    const newAlbum = await sut.createAlbum();
 
-		expect(apiMock.albumApi.createAlbum).toHaveBeenCalledTimes(1);
-		expect(apiMock.albumApi.createAlbum).toHaveBeenCalledWith({ createAlbumDto: payload });
-		expect(newAlbum).toEqual(returnedAlbum);
-	});
+    expect(apiMock.albumApi.createAlbum).toHaveBeenCalledTimes(1);
+    expect(apiMock.albumApi.createAlbum).toHaveBeenCalledWith({ createAlbumDto: payload });
+    expect(newAlbum).toEqual(returnedAlbum);
+  });
 
-	it('shows error message when it fails creating an album', async () => {
-		apiMock.albumApi.createAlbum.mockRejectedValueOnce({});
+  it('shows error message when it fails creating an album', async () => {
+    apiMock.albumApi.createAlbum.mockRejectedValueOnce({});
 
-		const newAlbum = await sut.createAlbum();
-		const notifications = get(notificationController.notificationList);
+    const newAlbum = await sut.createAlbum();
+    const notifications = get(notificationController.notificationList);
 
-		expect(apiMock.albumApi.createAlbum).toHaveBeenCalledTimes(1);
-		expect(newAlbum).not.toBeDefined();
-		expect(notifications).toHaveLength(1);
-		expect(notifications[0].type).toEqual(NotificationType.Error);
-	});
+    expect(apiMock.albumApi.createAlbum).toHaveBeenCalledTimes(1);
+    expect(newAlbum).not.toBeDefined();
+    expect(notifications).toHaveLength(1);
+    expect(notifications[0].type).toEqual(NotificationType.Error);
+  });
 
-	it('selects an album and deletes it', async () => {
-		apiMock.albumApi.deleteAlbum.mockResolvedValueOnce({
-			data: undefined,
-			config: {},
-			headers: {},
-			status: 200,
-			statusText: ''
-		});
+  it('selects an album and deletes it', async () => {
+    apiMock.albumApi.deleteAlbum.mockResolvedValueOnce({
+      data: undefined,
+      config: {},
+      headers: {},
+      status: 200,
+      statusText: '',
+    });
 
-		const albumToDelete = get(sut.albums)[2]; // delete third album
-		const albumToDeleteId = albumToDelete.id;
-		const contextMenuCoords = { x: 100, y: 150 };
+    const albumToDelete = get(sut.albums)[2]; // delete third album
+    const albumToDeleteId = albumToDelete.id;
+    const contextMenuCoords = { x: 100, y: 150 };
 
-		expect(get(sut.isShowContextMenu)).toBe(false);
-		sut.showAlbumContextMenu(contextMenuCoords, albumToDelete);
-		expect(get(sut.contextMenuPosition)).toEqual(contextMenuCoords);
-		expect(get(sut.isShowContextMenu)).toBe(true);
-		expect(get(sut.contextMenuTargetAlbum)).toEqual(albumToDelete);
+    expect(get(sut.isShowContextMenu)).toBe(false);
+    sut.showAlbumContextMenu(contextMenuCoords, albumToDelete);
+    expect(get(sut.contextMenuPosition)).toEqual(contextMenuCoords);
+    expect(get(sut.isShowContextMenu)).toBe(true);
+    expect(get(sut.contextMenuTargetAlbum)).toEqual(albumToDelete);
 
-		await sut.deleteAlbum(albumToDelete);
-		const updatedAlbums = get(sut.albums);
+    await sut.deleteAlbum(albumToDelete);
+    const updatedAlbums = get(sut.albums);
 
-		expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1);
-		expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledWith({ id: albumToDeleteId });
-		expect(updatedAlbums).toHaveLength(4);
-		expect(updatedAlbums).not.toContain(albumToDelete);
-	});
+    expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1);
+    expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledWith({ id: albumToDeleteId });
+    expect(updatedAlbums).toHaveLength(4);
+    expect(updatedAlbums).not.toContain(albumToDelete);
+  });
 
-	it('closes album context menu, deselecting album', () => {
-		const albumToDelete = get(sut.albums)[2]; // delete third album
-		sut.showAlbumContextMenu({ x: 100, y: 150 }, albumToDelete);
+  it('closes album context menu, deselecting album', () => {
+    const albumToDelete = get(sut.albums)[2]; // delete third album
+    sut.showAlbumContextMenu({ x: 100, y: 150 }, albumToDelete);
 
-		expect(get(sut.isShowContextMenu)).toBe(true);
+    expect(get(sut.isShowContextMenu)).toBe(true);
 
-		sut.closeAlbumContextMenu();
-		expect(get(sut.isShowContextMenu)).toBe(false);
-	});
+    sut.closeAlbumContextMenu();
+    expect(get(sut.isShowContextMenu)).toBe(false);
+  });
 });
diff --git a/web/src/routes/(user)/albums/albums.bloc.ts b/web/src/routes/(user)/albums/albums.bloc.ts
index 78cd907d56..6758be06b5 100644
--- a/web/src/routes/(user)/albums/albums.bloc.ts
+++ b/web/src/routes/(user)/albums/albums.bloc.ts
@@ -1,91 +1,88 @@
 import type { OnShowContextMenuDetail } from '$lib/components/album-page/album-card';
-import {
-	notificationController,
-	NotificationType
-} from '$lib/components/shared-components/notification/notification';
+import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
 import { AlbumResponseDto, api } from '@api';
 import { derived, get, writable } from 'svelte/store';
 
 type AlbumsProps = { albums: AlbumResponseDto[] };
 
 export const useAlbums = (props: AlbumsProps) => {
-	const albums = writable([...props.albums]);
-	const contextMenuPosition = writable<OnShowContextMenuDetail>({ x: 0, y: 0 });
-	const contextMenuTargetAlbum = writable<AlbumResponseDto | undefined>();
-	const isShowContextMenu = derived(contextMenuTargetAlbum, ($selectedAlbum) => !!$selectedAlbum);
+  const albums = writable([...props.albums]);
+  const contextMenuPosition = writable<OnShowContextMenuDetail>({ x: 0, y: 0 });
+  const contextMenuTargetAlbum = writable<AlbumResponseDto | undefined>();
+  const isShowContextMenu = derived(contextMenuTargetAlbum, ($selectedAlbum) => !!$selectedAlbum);
 
-	async function loadAlbums(): Promise<void> {
-		try {
-			const { data } = await api.albumApi.getAllAlbums();
-			albums.set(data);
+  async function loadAlbums(): Promise<void> {
+    try {
+      const { data } = await api.albumApi.getAllAlbums();
+      albums.set(data);
 
-			// Delete album that has no photos and is named 'Untitled'
-			for (const album of data) {
-				if (album.albumName === 'Untitled' && album.assetCount === 0) {
-					setTimeout(async () => {
-						await deleteAlbum(album);
-					}, 500);
-				}
-			}
-		} catch {
-			notificationController.show({
-				message: 'Error loading albums',
-				type: NotificationType.Error
-			});
-		}
-	}
+      // Delete album that has no photos and is named 'Untitled'
+      for (const album of data) {
+        if (album.albumName === 'Untitled' && album.assetCount === 0) {
+          setTimeout(async () => {
+            await deleteAlbum(album);
+          }, 500);
+        }
+      }
+    } catch {
+      notificationController.show({
+        message: 'Error loading albums',
+        type: NotificationType.Error,
+      });
+    }
+  }
 
-	async function createAlbum(): Promise<AlbumResponseDto | undefined> {
-		try {
-			const { data: newAlbum } = await api.albumApi.createAlbum({
-				createAlbumDto: {
-					albumName: 'Untitled'
-				}
-			});
+  async function createAlbum(): Promise<AlbumResponseDto | undefined> {
+    try {
+      const { data: newAlbum } = await api.albumApi.createAlbum({
+        createAlbumDto: {
+          albumName: 'Untitled',
+        },
+      });
 
-			return newAlbum;
-		} catch {
-			notificationController.show({
-				message: 'Error creating album',
-				type: NotificationType.Error
-			});
-		}
-	}
+      return newAlbum;
+    } catch {
+      notificationController.show({
+        message: 'Error creating album',
+        type: NotificationType.Error,
+      });
+    }
+  }
 
-	async function deleteAlbum(albumToDelete: AlbumResponseDto): Promise<void> {
-		await api.albumApi.deleteAlbum({ id: albumToDelete.id });
-		albums.set(
-			get(albums).filter(({ id }) => {
-				return id !== albumToDelete.id;
-			})
-		);
-	}
+  async function deleteAlbum(albumToDelete: AlbumResponseDto): Promise<void> {
+    await api.albumApi.deleteAlbum({ id: albumToDelete.id });
+    albums.set(
+      get(albums).filter(({ id }) => {
+        return id !== albumToDelete.id;
+      }),
+    );
+  }
 
-	async function showAlbumContextMenu(
-		contextMenuDetail: OnShowContextMenuDetail,
-		album: AlbumResponseDto
-	): Promise<void> {
-		contextMenuTargetAlbum.set(album);
+  async function showAlbumContextMenu(
+    contextMenuDetail: OnShowContextMenuDetail,
+    album: AlbumResponseDto,
+  ): Promise<void> {
+    contextMenuTargetAlbum.set(album);
 
-		contextMenuPosition.set({
-			x: contextMenuDetail.x,
-			y: contextMenuDetail.y
-		});
-	}
+    contextMenuPosition.set({
+      x: contextMenuDetail.x,
+      y: contextMenuDetail.y,
+    });
+  }
 
-	function closeAlbumContextMenu() {
-		contextMenuTargetAlbum.set(undefined);
-	}
+  function closeAlbumContextMenu() {
+    contextMenuTargetAlbum.set(undefined);
+  }
 
-	return {
-		albums,
-		isShowContextMenu,
-		contextMenuPosition,
-		contextMenuTargetAlbum,
-		loadAlbums,
-		createAlbum,
-		deleteAlbum,
-		showAlbumContextMenu,
-		closeAlbumContextMenu
-	};
+  return {
+    albums,
+    isShowContextMenu,
+    contextMenuPosition,
+    contextMenuTargetAlbum,
+    loadAlbums,
+    createAlbum,
+    deleteAlbum,
+    showAlbumContextMenu,
+    closeAlbumContextMenu,
+  };
 };
diff --git a/web/src/routes/(user)/archive/+page.server.ts b/web/src/routes/(user)/archive/+page.server.ts
index 3ba49ba37d..43e7c86524 100644
--- a/web/src/routes/(user)/archive/+page.server.ts
+++ b/web/src/routes/(user)/archive/+page.server.ts
@@ -1,16 +1,16 @@
-import type { PageServerLoad } from './$types';
-import { redirect } from '@sveltejs/kit';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Archive'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Archive',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/archive/+page.svelte b/web/src/routes/(user)/archive/+page.svelte
index c582493554..25bc1f2ff9 100644
--- a/web/src/routes/(user)/archive/+page.svelte
+++ b/web/src/routes/(user)/archive/+page.svelte
@@ -1,81 +1,75 @@
 <script lang="ts">
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
-	import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
-	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
-	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
-	import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
-	import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
-	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
-	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
-	import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
-	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
-	import SelectAll from 'svelte-material-icons/SelectAll.svelte';
-	import { archivedAsset } from '$lib/stores/archived-asset.store';
-	import { handleError } from '$lib/utils/handle-error';
-	import { api, AssetResponseDto } from '@api';
-	import { onMount } from 'svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import type { PageData } from './$types';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
+  import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
+  import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+  import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+  import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
+  import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
+  import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+  import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+  import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
+  import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
+  import SelectAll from 'svelte-material-icons/SelectAll.svelte';
+  import { archivedAsset } from '$lib/stores/archived-asset.store';
+  import { handleError } from '$lib/utils/handle-error';
+  import { api, AssetResponseDto } from '@api';
+  import { onMount } from 'svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import type { PageData } from './$types';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	let selectedAssets: Set<AssetResponseDto> = new Set();
-	$: isMultiSelectionMode = selectedAssets.size > 0;
-	$: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
+  let selectedAssets: Set<AssetResponseDto> = new Set();
+  $: isMultiSelectionMode = selectedAssets.size > 0;
+  $: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
 
-	onMount(async () => {
-		try {
-			const { data: assets } = await api.assetApi.getAllAssets({
-				isArchived: true,
-				withoutThumbs: true
-			});
-			$archivedAsset = assets;
-		} catch {
-			handleError(Error, 'Unable to load archived assets');
-		}
-	});
+  onMount(async () => {
+    try {
+      const { data: assets } = await api.assetApi.getAllAssets({
+        isArchived: true,
+        withoutThumbs: true,
+      });
+      $archivedAsset = assets;
+    } catch {
+      handleError(Error, 'Unable to load archived assets');
+    }
+  });
 
-	const onAssetDelete = (assetId: string) => {
-		$archivedAsset = $archivedAsset.filter((a) => a.id !== assetId);
-	};
-	const handleSelectAll = () => {
-		selectedAssets = new Set($archivedAsset);
-	};
+  const onAssetDelete = (assetId: string) => {
+    $archivedAsset = $archivedAsset.filter((a) => a.id !== assetId);
+  };
+  const handleSelectAll = () => {
+    selectedAssets = new Set($archivedAsset);
+  };
 </script>
 
 <UserPageLayout user={data.user} hideNavbar={isMultiSelectionMode} title={data.meta.title}>
-	<!-- Empty Message -->
-	{#if $archivedAsset.length === 0}
-		<EmptyPlaceholder
-			text="Archive photos and videos to hide them from your Photos view"
-			alt="Empty archive"
-		/>
-	{/if}
+  <!-- Empty Message -->
+  {#if $archivedAsset.length === 0}
+    <EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" alt="Empty archive" />
+  {/if}
 
-	<svelte:fragment slot="header">
-		{#if isMultiSelectionMode}
-			<AssetSelectControlBar
-				assets={selectedAssets}
-				clearSelect={() => (selectedAssets = new Set())}
-			>
-				<ArchiveAction unarchive onAssetArchive={(asset) => onAssetDelete(asset.id)} />
-				<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
-				<CreateSharedLink />
-				<AssetSelectContextMenu icon={Plus} title="Add">
-					<AddToAlbum />
-					<AddToAlbum shared />
-				</AssetSelectContextMenu>
-				<DeleteAssets {onAssetDelete} />
-				<AssetSelectContextMenu icon={DotsVertical} title="Add">
-					<DownloadAction menuItem />
-					<FavoriteAction menuItem removeFavorite={isAllFavorite} />
-				</AssetSelectContextMenu>
-			</AssetSelectControlBar>
-		{/if}
-	</svelte:fragment>
+  <svelte:fragment slot="header">
+    {#if isMultiSelectionMode}
+      <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
+        <ArchiveAction unarchive onAssetArchive={(asset) => onAssetDelete(asset.id)} />
+        <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
+        <CreateSharedLink />
+        <AssetSelectContextMenu icon={Plus} title="Add">
+          <AddToAlbum />
+          <AddToAlbum shared />
+        </AssetSelectContextMenu>
+        <DeleteAssets {onAssetDelete} />
+        <AssetSelectContextMenu icon={DotsVertical} title="Add">
+          <DownloadAction menuItem />
+          <FavoriteAction menuItem removeFavorite={isAllFavorite} />
+        </AssetSelectContextMenu>
+      </AssetSelectControlBar>
+    {/if}
+  </svelte:fragment>
 
-	<GalleryViewer assets={$archivedAsset} bind:selectedAssets viewFrom="archive-page" />
+  <GalleryViewer assets={$archivedAsset} bind:selectedAssets viewFrom="archive-page" />
 </UserPageLayout>
diff --git a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts b/web/src/routes/(user)/archive/photos/[assetId]/+page.ts
index 80c499ee8b..88923a1af5 100644
--- a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts
+++ b/web/src/routes/(user)/archive/photos/[assetId]/+page.ts
@@ -1,13 +1,13 @@
-import { redirect } from '@sveltejs/kit';
-export const prerender = false;
-import type { PageLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageLoad } from './$types';
+export const prerender = false;
 
 export const load: PageLoad = async ({ parent }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	throw redirect(302, AppRoute.ARCHIVE);
+  throw redirect(302, AppRoute.ARCHIVE);
 };
diff --git a/web/src/routes/(user)/explore/+page.server.ts b/web/src/routes/(user)/explore/+page.server.ts
index 44473aa3aa..97b674cb38 100644
--- a/web/src/routes/(user)/explore/+page.server.ts
+++ b/web/src/routes/(user)/explore/+page.server.ts
@@ -1,21 +1,21 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ locals, parent }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const { data: items } = await locals.api.searchApi.getExploreData();
-	const { data: people } = await locals.api.personApi.getAllPeople();
-	return {
-		user,
-		items,
-		people,
-		meta: {
-			title: 'Explore'
-		}
-	};
+  const { data: items } = await locals.api.searchApi.getExploreData();
+  const { data: people } = await locals.api.personApi.getAllPeople();
+  return {
+    user,
+    items,
+    people,
+    meta: {
+      title: 'Explore',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte
index 4405a55db1..68f59d8870 100644
--- a/web/src/routes/(user)/explore/+page.svelte
+++ b/web/src/routes/(user)/explore/+page.svelte
@@ -1,157 +1,157 @@
 <script lang="ts">
-	import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
-	import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import { AppRoute } from '$lib/constants';
-	import { AssetTypeEnum, SearchExploreResponseDto, api } from '@api';
-	import ClockOutline from 'svelte-material-icons/ClockOutline.svelte';
-	import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte';
-	import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
-	import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
-	import type { PageData } from './$types';
+  import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
+  import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import { AppRoute } from '$lib/constants';
+  import { AssetTypeEnum, SearchExploreResponseDto, api } from '@api';
+  import ClockOutline from 'svelte-material-icons/ClockOutline.svelte';
+  import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte';
+  import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
+  import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	enum Field {
-		CITY = 'exifInfo.city',
-		TAGS = 'smartInfo.tags',
-		OBJECTS = 'smartInfo.objects'
-	}
+  enum Field {
+    CITY = 'exifInfo.city',
+    TAGS = 'smartInfo.tags',
+    OBJECTS = 'smartInfo.objects',
+  }
 
-	const MAX_ITEMS = 12;
+  const MAX_ITEMS = 12;
 
-	const getFieldItems = (items: SearchExploreResponseDto[], field: Field) => {
-		const targetField = items.find((item) => item.fieldName === field);
-		return targetField?.items || [];
-	};
+  const getFieldItems = (items: SearchExploreResponseDto[], field: Field) => {
+    const targetField = items.find((item) => item.fieldName === field);
+    return targetField?.items || [];
+  };
 
-	$: things = getFieldItems(data.items, Field.OBJECTS);
-	$: places = getFieldItems(data.items, Field.CITY);
-	$: people = data.people.slice(0, MAX_ITEMS);
+  $: things = getFieldItems(data.items, Field.OBJECTS);
+  $: places = getFieldItems(data.items, Field.CITY);
+  $: people = data.people.slice(0, MAX_ITEMS);
 </script>
 
 <UserPageLayout user={data.user} title={data.meta.title}>
-	{#if people.length > 0}
-		<div class="mb-6 mt-2">
-			<div class="flex justify-between">
-				<p class="mb-4 dark:text-immich-dark-fg font-medium">People</p>
-				{#if data.people.length > MAX_ITEMS}
-					<a
-						href={AppRoute.PEOPLE}
-						class="font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary dark:text-immich-dark-fg"
-						draggable="false">View All</a
-					>
-				{/if}
-			</div>
-			<div class="flex flex-row flex-wrap gap-4">
-				{#each people as person (person.id)}
-					<a href="/people/{person.id}" class="w-24 text-center">
-						<ImageThumbnail
-							circle
-							shadow
-							url={api.getPeopleThumbnailUrl(person.id)}
-							altText={person.name}
-							widthStyle="100%"
-						/>
-						<p class="font-medium mt-2 text-ellipsis text-sm dark:text-white">{person.name}</p>
-					</a>
-				{/each}
-			</div>
-		</div>
-	{/if}
+  {#if people.length > 0}
+    <div class="mb-6 mt-2">
+      <div class="flex justify-between">
+        <p class="mb-4 dark:text-immich-dark-fg font-medium">People</p>
+        {#if data.people.length > MAX_ITEMS}
+          <a
+            href={AppRoute.PEOPLE}
+            class="font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary dark:text-immich-dark-fg"
+            draggable="false">View All</a
+          >
+        {/if}
+      </div>
+      <div class="flex flex-row flex-wrap gap-4">
+        {#each people as person (person.id)}
+          <a href="/people/{person.id}" class="w-24 text-center">
+            <ImageThumbnail
+              circle
+              shadow
+              url={api.getPeopleThumbnailUrl(person.id)}
+              altText={person.name}
+              widthStyle="100%"
+            />
+            <p class="font-medium mt-2 text-ellipsis text-sm dark:text-white">{person.name}</p>
+          </a>
+        {/each}
+      </div>
+    </div>
+  {/if}
 
-	{#if places.length > 0}
-		<div class="mb-6 mt-2">
-			<div>
-				<p class="mb-4 dark:text-immich-dark-fg font-medium">Places</p>
-			</div>
-			<div class="flex flex-row flex-wrap gap-4">
-				{#each places as item}
-					<a class="relative" href="/search?{Field.CITY}={item.value}" draggable="false">
-						<div
-							class="filter brightness-75 rounded-xl overflow-hidden w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] flex justify-center"
-						>
-							<Thumbnail thumbnailSize={156} asset={item.data} readonly />
-						</div>
-						<span
-							class="capitalize absolute bottom-2 w-full text-center text-sm font-medium text-white text-ellipsis w-100 px-1 hover:cursor-pointer backdrop-blur-[1px]"
-						>
-							{item.value}
-						</span>
-					</a>
-				{/each}
-			</div>
-		</div>
-	{/if}
+  {#if places.length > 0}
+    <div class="mb-6 mt-2">
+      <div>
+        <p class="mb-4 dark:text-immich-dark-fg font-medium">Places</p>
+      </div>
+      <div class="flex flex-row flex-wrap gap-4">
+        {#each places as item}
+          <a class="relative" href="/search?{Field.CITY}={item.value}" draggable="false">
+            <div
+              class="filter brightness-75 rounded-xl overflow-hidden w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] flex justify-center"
+            >
+              <Thumbnail thumbnailSize={156} asset={item.data} readonly />
+            </div>
+            <span
+              class="capitalize absolute bottom-2 w-full text-center text-sm font-medium text-white text-ellipsis w-100 px-1 hover:cursor-pointer backdrop-blur-[1px]"
+            >
+              {item.value}
+            </span>
+          </a>
+        {/each}
+      </div>
+    </div>
+  {/if}
 
-	{#if things.length > 0}
-		<div class="mb-6 mt-2">
-			<div>
-				<p class="mb-4 dark:text-immich-dark-fg font-medium">Things</p>
-			</div>
-			<div class="flex flex-row flex-wrap gap-4">
-				{#each things as item}
-					<a class="relative" href="/search?{Field.OBJECTS}={item.value}" draggable="false">
-						<div
-							class="filter brightness-75 rounded-xl overflow-hidden w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center flex"
-						>
-							<Thumbnail thumbnailSize={156} asset={item.data} readonly />
-						</div>
-						<span
-							class="capitalize absolute bottom-2 w-full text-center text-sm font-medium text-white text-ellipsis w-100 px-1 hover:cursor-pointer backdrop-blur-[1px]"
-						>
-							{item.value}
-						</span>
-					</a>
-				{/each}
-			</div>
-		</div>
-	{/if}
+  {#if things.length > 0}
+    <div class="mb-6 mt-2">
+      <div>
+        <p class="mb-4 dark:text-immich-dark-fg font-medium">Things</p>
+      </div>
+      <div class="flex flex-row flex-wrap gap-4">
+        {#each things as item}
+          <a class="relative" href="/search?{Field.OBJECTS}={item.value}" draggable="false">
+            <div
+              class="filter brightness-75 rounded-xl overflow-hidden w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center flex"
+            >
+              <Thumbnail thumbnailSize={156} asset={item.data} readonly />
+            </div>
+            <span
+              class="capitalize absolute bottom-2 w-full text-center text-sm font-medium text-white text-ellipsis w-100 px-1 hover:cursor-pointer backdrop-blur-[1px]"
+            >
+              {item.value}
+            </span>
+          </a>
+        {/each}
+      </div>
+    </div>
+  {/if}
 
-	<hr class="dark:border-immich-dark-gray mb-4" />
+  <hr class="dark:border-immich-dark-gray mb-4" />
 
-	<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-8">
-		<div class="flex flex-col gap-6 dark:text-immich-dark-fg">
-			<p class="text-sm">YOUR ACTIVITY</p>
-			<div class="flex flex-col gap-4 dark:text-immich-dark-fg/80">
-				<a
-					href={AppRoute.FAVORITES}
-					class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary content-center gap-2"
-					draggable="false"
-				>
-					<HeartMultipleOutline size={24} />
-					<span>Favorites</span>
-				</a>
-				<a
-					href="/search?recent=true"
-					class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary content-center gap-2"
-					draggable="false"
-				>
-					<ClockOutline size={24} />
-					<span>Recently added</span>
-				</a>
-			</div>
-		</div>
-		<div class="flex flex-col gap-6 dark:text-immich-dark-fg">
-			<p class="text-sm">CATEGORIES</p>
-			<div class="flex flex-col gap-4 dark:text-immich-dark-fg/80">
-				<a
-					href="/search?type={AssetTypeEnum.Video}"
-					class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary items-center gap-2"
-				>
-					<PlayCircleOutline size={24} />
-					<span>Videos</span>
-				</a>
-				<div>
-					<a
-						href="/search?motion=true"
-						class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary items-center gap-2"
-					>
-						<MotionPlayOutline size={24} />
-						<span>Motion photos</span>
-					</a>
-				</div>
-			</div>
-		</div>
-	</div>
+  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-8">
+    <div class="flex flex-col gap-6 dark:text-immich-dark-fg">
+      <p class="text-sm">YOUR ACTIVITY</p>
+      <div class="flex flex-col gap-4 dark:text-immich-dark-fg/80">
+        <a
+          href={AppRoute.FAVORITES}
+          class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary content-center gap-2"
+          draggable="false"
+        >
+          <HeartMultipleOutline size={24} />
+          <span>Favorites</span>
+        </a>
+        <a
+          href="/search?recent=true"
+          class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary content-center gap-2"
+          draggable="false"
+        >
+          <ClockOutline size={24} />
+          <span>Recently added</span>
+        </a>
+      </div>
+    </div>
+    <div class="flex flex-col gap-6 dark:text-immich-dark-fg">
+      <p class="text-sm">CATEGORIES</p>
+      <div class="flex flex-col gap-4 dark:text-immich-dark-fg/80">
+        <a
+          href="/search?type={AssetTypeEnum.Video}"
+          class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary items-center gap-2"
+        >
+          <PlayCircleOutline size={24} />
+          <span>Videos</span>
+        </a>
+        <div>
+          <a
+            href="/search?motion=true"
+            class="w-full flex text-sm font-medium hover:text-immich-primary dark:hover:text-immich-dark-primary items-center gap-2"
+          >
+            <MotionPlayOutline size={24} />
+            <span>Motion photos</span>
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
 </UserPageLayout>
diff --git a/web/src/routes/(user)/favorites/+page.server.ts b/web/src/routes/(user)/favorites/+page.server.ts
index 5ac8e27121..d65255b45f 100644
--- a/web/src/routes/(user)/favorites/+page.server.ts
+++ b/web/src/routes/(user)/favorites/+page.server.ts
@@ -1,16 +1,16 @@
-import type { PageServerLoad } from './$types';
-import { redirect } from '@sveltejs/kit';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Favorites'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Favorites',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/favorites/+page.svelte b/web/src/routes/(user)/favorites/+page.svelte
index 56149c7791..3a397ceb6e 100644
--- a/web/src/routes/(user)/favorites/+page.svelte
+++ b/web/src/routes/(user)/favorites/+page.svelte
@@ -1,82 +1,79 @@
 <script lang="ts">
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
-	import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
-	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
-	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
-	import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
-	import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
-	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
-	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
-	import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
-	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
-	import { handleError } from '$lib/utils/handle-error';
-	import { api, AssetResponseDto } from '@api';
-	import { onMount } from 'svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import Error from '../../+error.svelte';
-	import type { PageData } from './$types';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import SelectAll from 'svelte-material-icons/SelectAll.svelte';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
+  import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
+  import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+  import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+  import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
+  import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
+  import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+  import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+  import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
+  import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
+  import { handleError } from '$lib/utils/handle-error';
+  import { api, AssetResponseDto } from '@api';
+  import { onMount } from 'svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import Error from '../../+error.svelte';
+  import type { PageData } from './$types';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import SelectAll from 'svelte-material-icons/SelectAll.svelte';
 
-	let favorites: AssetResponseDto[] = [];
-	let selectedAssets: Set<AssetResponseDto> = new Set();
+  let favorites: AssetResponseDto[] = [];
+  let selectedAssets: Set<AssetResponseDto> = new Set();
 
-	export let data: PageData;
+  export let data: PageData;
 
-	$: isMultiSelectionMode = selectedAssets.size > 0;
-	$: isAllArchive = Array.from(selectedAssets).every((asset) => asset.isArchived);
+  $: isMultiSelectionMode = selectedAssets.size > 0;
+  $: isAllArchive = Array.from(selectedAssets).every((asset) => asset.isArchived);
 
-	onMount(async () => {
-		try {
-			const { data: assets } = await api.assetApi.getAllAssets({
-				isFavorite: true,
-				withoutThumbs: true
-			});
-			favorites = assets;
-		} catch {
-			handleError(Error, 'Unable to load favorites');
-		}
-	});
+  onMount(async () => {
+    try {
+      const { data: assets } = await api.assetApi.getAllAssets({
+        isFavorite: true,
+        withoutThumbs: true,
+      });
+      favorites = assets;
+    } catch {
+      handleError(Error, 'Unable to load favorites');
+    }
+  });
 
-	const handleSelectAll = () => {
-		selectedAssets = new Set(favorites);
-	};
+  const handleSelectAll = () => {
+    selectedAssets = new Set(favorites);
+  };
 
-	const onAssetDelete = (assetId: string) => {
-		favorites = favorites.filter((a) => a.id !== assetId);
-	};
+  const onAssetDelete = (assetId: string) => {
+    favorites = favorites.filter((a) => a.id !== assetId);
+  };
 </script>
 
 <!-- Multiselection mode app bar -->
 {#if isMultiSelectionMode}
-	<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
-		<FavoriteAction removeFavorite onAssetFavorite={(asset) => onAssetDelete(asset.id)} />
-		<CreateSharedLink />
-		<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
-		<AssetSelectContextMenu icon={Plus} title="Add">
-			<AddToAlbum />
-			<AddToAlbum shared />
-		</AssetSelectContextMenu>
-		<DeleteAssets {onAssetDelete} />
-		<AssetSelectContextMenu icon={DotsVertical} title="Menu">
-			<DownloadAction menuItem />
-			<ArchiveAction menuItem unarchive={isAllArchive} />
-		</AssetSelectContextMenu>
-	</AssetSelectControlBar>
+  <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
+    <FavoriteAction removeFavorite onAssetFavorite={(asset) => onAssetDelete(asset.id)} />
+    <CreateSharedLink />
+    <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
+    <AssetSelectContextMenu icon={Plus} title="Add">
+      <AddToAlbum />
+      <AddToAlbum shared />
+    </AssetSelectContextMenu>
+    <DeleteAssets {onAssetDelete} />
+    <AssetSelectContextMenu icon={DotsVertical} title="Menu">
+      <DownloadAction menuItem />
+      <ArchiveAction menuItem unarchive={isAllArchive} />
+    </AssetSelectContextMenu>
+  </AssetSelectControlBar>
 {/if}
 
 <UserPageLayout user={data.user} hideNavbar={isMultiSelectionMode} title={data.meta.title}>
-	<section>
-		<!-- Empty Message -->
-		{#if favorites.length === 0}
-			<EmptyPlaceholder
-				text="Add favorites to quickly find your best pictures and videos"
-				alt="Empty favorites"
-			/>
-		{/if}
+  <section>
+    <!-- Empty Message -->
+    {#if favorites.length === 0}
+      <EmptyPlaceholder text="Add favorites to quickly find your best pictures and videos" alt="Empty favorites" />
+    {/if}
 
-		<GalleryViewer assets={favorites} bind:selectedAssets viewFrom="favorites-page" />
-	</section>
+    <GalleryViewer assets={favorites} bind:selectedAssets viewFrom="favorites-page" />
+  </section>
 </UserPageLayout>
diff --git a/web/src/routes/(user)/favorites/[assetId]/+page.server.ts b/web/src/routes/(user)/favorites/[assetId]/+page.server.ts
index 4a553a1928..fd6aa42d4c 100644
--- a/web/src/routes/(user)/favorites/[assetId]/+page.server.ts
+++ b/web/src/routes/(user)/favorites/[assetId]/+page.server.ts
@@ -1,15 +1,15 @@
 import { redirect } from '@sveltejs/kit';
 export const prerender = false;
 
-import type { PageServerLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import type { PageServerLoad } from './$types';
 
 export const load: PageServerLoad = async ({ parent }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else {
-		throw redirect(302, AppRoute.FAVORITES);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else {
+    throw redirect(302, AppRoute.FAVORITES);
+  }
 };
diff --git a/web/src/routes/(user)/map/+page.server.ts b/web/src/routes/(user)/map/+page.server.ts
index 6bcb95791e..50f450fa6a 100644
--- a/web/src/routes/(user)/map/+page.server.ts
+++ b/web/src/routes/(user)/map/+page.server.ts
@@ -3,14 +3,14 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Map'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Map',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/map/+page.svelte b/web/src/routes/(user)/map/+page.svelte
index cdf1fec3e1..b4301ba780 100644
--- a/web/src/routes/(user)/map/+page.svelte
+++ b/web/src/routes/(user)/map/+page.svelte
@@ -1,176 +1,172 @@
 <script lang="ts">
-	import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import MapSettingsModal from '$lib/components/map-page/map-settings-modal.svelte';
-	import Portal from '$lib/components/shared-components/portal/portal.svelte';
-	import {
-		assetInteractionStore,
-		isViewingAssetStoreState,
-		viewingAssetStoreState
-	} from '$lib/stores/asset-interaction.store';
-	import { mapSettings } from '$lib/stores/preferences.store';
-	import { MapMarkerResponseDto, api } from '@api';
-	import { isEqual, omit } from 'lodash-es';
-	import { onDestroy, onMount } from 'svelte';
-	import Cog from 'svelte-material-icons/Cog.svelte';
-	import type { PageData } from './$types';
-	import { DateTime, Duration } from 'luxon';
+  import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import MapSettingsModal from '$lib/components/map-page/map-settings-modal.svelte';
+  import Portal from '$lib/components/shared-components/portal/portal.svelte';
+  import {
+    assetInteractionStore,
+    isViewingAssetStoreState,
+    viewingAssetStoreState,
+  } from '$lib/stores/asset-interaction.store';
+  import { mapSettings } from '$lib/stores/preferences.store';
+  import { MapMarkerResponseDto, api } from '@api';
+  import { isEqual, omit } from 'lodash-es';
+  import { onDestroy, onMount } from 'svelte';
+  import Cog from 'svelte-material-icons/Cog.svelte';
+  import type { PageData } from './$types';
+  import { DateTime, Duration } from 'luxon';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	let leaflet: typeof import('$lib/components/shared-components/leaflet');
-	let mapMarkers: MapMarkerResponseDto[] = [];
-	let abortController: AbortController;
-	let viewingAssets: string[] = [];
-	let viewingAssetCursor = 0;
-	let showSettingsModal = false;
+  let leaflet: typeof import('$lib/components/shared-components/leaflet');
+  let mapMarkers: MapMarkerResponseDto[] = [];
+  let abortController: AbortController;
+  let viewingAssets: string[] = [];
+  let viewingAssetCursor = 0;
+  let showSettingsModal = false;
 
-	onMount(() => {
-		loadMapMarkers().then((data) => (mapMarkers = data));
-		import('$lib/components/shared-components/leaflet').then((data) => (leaflet = data));
-	});
+  onMount(() => {
+    loadMapMarkers().then((data) => (mapMarkers = data));
+    import('$lib/components/shared-components/leaflet').then((data) => (leaflet = data));
+  });
 
-	onDestroy(() => {
-		if (abortController) {
-			abortController.abort();
-		}
-		assetInteractionStore.clearMultiselect();
-		assetInteractionStore.setIsViewingAsset(false);
-	});
+  onDestroy(() => {
+    if (abortController) {
+      abortController.abort();
+    }
+    assetInteractionStore.clearMultiselect();
+    assetInteractionStore.setIsViewingAsset(false);
+  });
 
-	async function loadMapMarkers() {
-		if (abortController) {
-			abortController.abort();
-		}
-		abortController = new AbortController();
+  async function loadMapMarkers() {
+    if (abortController) {
+      abortController.abort();
+    }
+    abortController = new AbortController();
 
-		const { onlyFavorites } = $mapSettings;
-		const { fileCreatedAfter, fileCreatedBefore } = getFileCreatedDates();
+    const { onlyFavorites } = $mapSettings;
+    const { fileCreatedAfter, fileCreatedBefore } = getFileCreatedDates();
 
-		const { data } = await api.assetApi.getMapMarkers(
-			{
-				isFavorite: onlyFavorites || undefined,
-				fileCreatedAfter: fileCreatedAfter || undefined,
-				fileCreatedBefore
-			},
-			{
-				signal: abortController.signal
-			}
-		);
-		return data;
-	}
+    const { data } = await api.assetApi.getMapMarkers(
+      {
+        isFavorite: onlyFavorites || undefined,
+        fileCreatedAfter: fileCreatedAfter || undefined,
+        fileCreatedBefore,
+      },
+      {
+        signal: abortController.signal,
+      },
+    );
+    return data;
+  }
 
-	function getFileCreatedDates() {
-		const { relativeDate, dateAfter, dateBefore } = $mapSettings;
+  function getFileCreatedDates() {
+    const { relativeDate, dateAfter, dateBefore } = $mapSettings;
 
-		if (relativeDate) {
-			const duration = Duration.fromISO(relativeDate);
-			return {
-				fileCreatedAfter: duration.isValid ? DateTime.now().minus(duration).toISO() : undefined
-			};
-		}
+    if (relativeDate) {
+      const duration = Duration.fromISO(relativeDate);
+      return {
+        fileCreatedAfter: duration.isValid ? DateTime.now().minus(duration).toISO() : undefined,
+      };
+    }
 
-		try {
-			return {
-				fileCreatedAfter: dateAfter ? new Date(dateAfter).toISOString() : undefined,
-				fileCreatedBefore: dateBefore ? new Date(dateBefore).toISOString() : undefined
-			};
-		} catch {
-			$mapSettings.dateAfter = '';
-			$mapSettings.dateBefore = '';
-			return {};
-		}
-	}
+    try {
+      return {
+        fileCreatedAfter: dateAfter ? new Date(dateAfter).toISOString() : undefined,
+        fileCreatedBefore: dateBefore ? new Date(dateBefore).toISOString() : undefined,
+      };
+    } catch {
+      $mapSettings.dateAfter = '';
+      $mapSettings.dateBefore = '';
+      return {};
+    }
+  }
 
-	function onViewAssets(assetIds: string[], activeAssetIndex: number) {
-		assetInteractionStore.setViewingAssetId(assetIds[activeAssetIndex]);
-		viewingAssets = assetIds;
-		viewingAssetCursor = activeAssetIndex;
-	}
+  function onViewAssets(assetIds: string[], activeAssetIndex: number) {
+    assetInteractionStore.setViewingAssetId(assetIds[activeAssetIndex]);
+    viewingAssets = assetIds;
+    viewingAssetCursor = activeAssetIndex;
+  }
 
-	function navigateNext() {
-		if (viewingAssetCursor < viewingAssets.length - 1) {
-			assetInteractionStore.setViewingAssetId(viewingAssets[++viewingAssetCursor]);
-		}
-	}
+  function navigateNext() {
+    if (viewingAssetCursor < viewingAssets.length - 1) {
+      assetInteractionStore.setViewingAssetId(viewingAssets[++viewingAssetCursor]);
+    }
+  }
 
-	function navigatePrevious() {
-		if (viewingAssetCursor > 0) {
-			assetInteractionStore.setViewingAssetId(viewingAssets[--viewingAssetCursor]);
-		}
-	}
+  function navigatePrevious() {
+    if (viewingAssetCursor > 0) {
+      assetInteractionStore.setViewingAssetId(viewingAssets[--viewingAssetCursor]);
+    }
+  }
 </script>
 
 <UserPageLayout user={data.user} title={data.meta.title}>
-	<div class="h-full w-full isolate">
-		{#if leaflet}
-			{@const { Map, TileLayer, AssetMarkerCluster, Control } = leaflet}
-			<Map
-				center={[30, 0]}
-				zoom={3}
-				allowDarkMode={$mapSettings.allowDarkMode}
-				options={{
-					maxBounds: [
-						[-90, -180],
-						[90, 180]
-					],
-					minZoom: 2.5
-				}}
-			>
-				<TileLayer
-					urlTemplate={'https://tile.openstreetmap.org/{z}/{x}/{y}.png'}
-					options={{
-						attribution:
-							'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
-					}}
-				/>
-				<AssetMarkerCluster
-					markers={mapMarkers}
-					on:view={({ detail }) => onViewAssets(detail.assetIds, detail.activeAssetIndex)}
-				/>
-				<Control>
-					<button
-						class="flex justify-center items-center bg-white text-black/70 w-8 h-8 font-bold rounded-sm border-2 border-black/20 hover:bg-gray-50 focus:bg-gray-50"
-						title="Open map settings"
-						on:click={() => (showSettingsModal = true)}
-					>
-						<Cog size="100%" class="p-1" />
-					</button>
-				</Control>
-			</Map>
-		{/if}
-	</div>
+  <div class="h-full w-full isolate">
+    {#if leaflet}
+      {@const { Map, TileLayer, AssetMarkerCluster, Control } = leaflet}
+      <Map
+        center={[30, 0]}
+        zoom={3}
+        allowDarkMode={$mapSettings.allowDarkMode}
+        options={{
+          maxBounds: [
+            [-90, -180],
+            [90, 180],
+          ],
+          minZoom: 2.5,
+        }}
+      >
+        <TileLayer
+          urlTemplate={'https://tile.openstreetmap.org/{z}/{x}/{y}.png'}
+          options={{
+            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
+          }}
+        />
+        <AssetMarkerCluster
+          markers={mapMarkers}
+          on:view={({ detail }) => onViewAssets(detail.assetIds, detail.activeAssetIndex)}
+        />
+        <Control>
+          <button
+            class="flex justify-center items-center bg-white text-black/70 w-8 h-8 font-bold rounded-sm border-2 border-black/20 hover:bg-gray-50 focus:bg-gray-50"
+            title="Open map settings"
+            on:click={() => (showSettingsModal = true)}
+          >
+            <Cog size="100%" class="p-1" />
+          </button>
+        </Control>
+      </Map>
+    {/if}
+  </div>
 </UserPageLayout>
 
 <Portal target="body">
-	{#if $isViewingAssetStoreState}
-		<AssetViewer
-			asset={$viewingAssetStoreState}
-			showNavigation={viewingAssets.length > 1}
-			on:navigate-next={navigateNext}
-			on:navigate-previous={navigatePrevious}
-			on:close={() => {
-				assetInteractionStore.setIsViewingAsset(false);
-			}}
-		/>
-	{/if}
+  {#if $isViewingAssetStoreState}
+    <AssetViewer
+      asset={$viewingAssetStoreState}
+      showNavigation={viewingAssets.length > 1}
+      on:navigate-next={navigateNext}
+      on:navigate-previous={navigatePrevious}
+      on:close={() => {
+        assetInteractionStore.setIsViewingAsset(false);
+      }}
+    />
+  {/if}
 </Portal>
 
 {#if showSettingsModal}
-	<MapSettingsModal
-		settings={{ ...$mapSettings }}
-		on:close={() => (showSettingsModal = false)}
-		on:save={async ({ detail }) => {
-			const shouldUpdate = !isEqual(
-				omit(detail, 'allowDarkMode'),
-				omit($mapSettings, 'allowDarkMode')
-			);
-			showSettingsModal = false;
-			$mapSettings = detail;
+  <MapSettingsModal
+    settings={{ ...$mapSettings }}
+    on:close={() => (showSettingsModal = false)}
+    on:save={async ({ detail }) => {
+      const shouldUpdate = !isEqual(omit(detail, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode'));
+      showSettingsModal = false;
+      $mapSettings = detail;
 
-			if (shouldUpdate) {
-				mapMarkers = await loadMapMarkers();
-			}
-		}}
-	/>
+      if (shouldUpdate) {
+        mapMarkers = await loadMapMarkers();
+      }
+    }}
+  />
 {/if}
diff --git a/web/src/routes/(user)/memory/+page.server.ts b/web/src/routes/(user)/memory/+page.server.ts
index 88bba26af2..3973762d7b 100644
--- a/web/src/routes/(user)/memory/+page.server.ts
+++ b/web/src/routes/(user)/memory/+page.server.ts
@@ -1,16 +1,16 @@
-import type { PageServerLoad } from './$types';
-import { redirect } from '@sveltejs/kit';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Memory'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Memory',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/memory/+page.svelte b/web/src/routes/(user)/memory/+page.svelte
index 28a4f538c7..7f34913224 100644
--- a/web/src/routes/(user)/memory/+page.svelte
+++ b/web/src/routes/(user)/memory/+page.svelte
@@ -1,5 +1,5 @@
 <script>
-	import MemoryViewer from '$lib/components/memory-page/memory-viewer.svelte';
+  import MemoryViewer from '$lib/components/memory-page/memory-viewer.svelte';
 </script>
 
 <MemoryViewer />
diff --git a/web/src/routes/(user)/memory/photos/+page.server.ts b/web/src/routes/(user)/memory/photos/+page.server.ts
index b7c5e39912..bd32438aa0 100644
--- a/web/src/routes/(user)/memory/photos/+page.server.ts
+++ b/web/src/routes/(user)/memory/photos/+page.server.ts
@@ -1,15 +1,15 @@
 import { redirect } from '@sveltejs/kit';
 export const prerender = false;
 
-import type { PageServerLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import type { PageServerLoad } from './$types';
 
 export const load: PageServerLoad = async ({ parent }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else {
-		throw redirect(302, AppRoute.MEMORY);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else {
+    throw redirect(302, AppRoute.MEMORY);
+  }
 };
diff --git a/web/src/routes/(user)/memory/photos/[assetId]/+page.server.ts b/web/src/routes/(user)/memory/photos/[assetId]/+page.server.ts
index b7c5e39912..bd32438aa0 100644
--- a/web/src/routes/(user)/memory/photos/[assetId]/+page.server.ts
+++ b/web/src/routes/(user)/memory/photos/[assetId]/+page.server.ts
@@ -1,15 +1,15 @@
 import { redirect } from '@sveltejs/kit';
 export const prerender = false;
 
-import type { PageServerLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import type { PageServerLoad } from './$types';
 
 export const load: PageServerLoad = async ({ parent }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else {
-		throw redirect(302, AppRoute.MEMORY);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else {
+    throw redirect(302, AppRoute.MEMORY);
+  }
 };
diff --git a/web/src/routes/(user)/partners/[userId]/+page.server.ts b/web/src/routes/(user)/partners/[userId]/+page.server.ts
index 8ce5f77606..58ed9fb3d9 100644
--- a/web/src/routes/(user)/partners/[userId]/+page.server.ts
+++ b/web/src/routes/(user)/partners/[userId]/+page.server.ts
@@ -1,21 +1,21 @@
-import type { PageServerLoad } from './$types';
-import { redirect } from '@sveltejs/kit';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
 
 export const load: PageServerLoad = async ({ params, parent, locals: { api } }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const { data: partner } = await api.userApi.getUserById({ userId: params['userId'] });
+  const { data: partner } = await api.userApi.getUserById({ userId: params['userId'] });
 
-	return {
-		user,
-		partner,
-		meta: {
-			title: 'Partner'
-		}
-	};
+  return {
+    user,
+    partner,
+    meta: {
+      title: 'Partner',
+    },
+  };
 };
diff --git a/web/src/routes/(user)/partners/[userId]/+page.svelte b/web/src/routes/(user)/partners/[userId]/+page.svelte
index 2bfae57bb1..e5f25dc69e 100644
--- a/web/src/routes/(user)/partners/[userId]/+page.svelte
+++ b/web/src/routes/(user)/partners/[userId]/+page.svelte
@@ -1,62 +1,48 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
-	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
-	import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
-	import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
-	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
-	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import { AppRoute } from '$lib/constants';
-	import {
-		assetInteractionStore,
-		isMultiSelectStoreState,
-		selectedAssets
-	} from '$lib/stores/asset-interaction.store';
-	import { onDestroy } from 'svelte';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import type { PageData } from './$types';
+  import { goto } from '$app/navigation';
+  import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
+  import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+  import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
+  import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
+  import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+  import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+  import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
+  import { AppRoute } from '$lib/constants';
+  import { assetInteractionStore, isMultiSelectStoreState, selectedAssets } from '$lib/stores/asset-interaction.store';
+  import { onDestroy } from 'svelte';
+  import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	onDestroy(() => {
-		assetInteractionStore.clearMultiselect();
-	});
+  onDestroy(() => {
+    assetInteractionStore.clearMultiselect();
+  });
 </script>
 
 <main class="grid h-screen pt-18 bg-immich-bg dark:bg-immich-dark-bg">
-	{#if $isMultiSelectStoreState}
-		<AssetSelectControlBar
-			assets={$selectedAssets}
-			clearSelect={assetInteractionStore.clearMultiselect}
-		>
-			<DownloadAction />
-		</AssetSelectControlBar>
-		<AssetSelectControlBar
-			assets={$selectedAssets}
-			clearSelect={assetInteractionStore.clearMultiselect}
-		>
-			<CreateSharedLink />
-			<AssetSelectContextMenu icon={Plus} title="Add">
-				<AddToAlbum />
-				<AddToAlbum shared />
-			</AssetSelectContextMenu>
-			<DownloadAction />
-		</AssetSelectControlBar>
-	{:else}
-		<ControlAppBar
-			showBackButton
-			backIcon={ArrowLeft}
-			on:close-button-click={() => goto(AppRoute.SHARING)}
-		>
-			<svelte:fragment slot="leading">
-				<p class="text-immich-fg dark:text-immich-dark-fg">
-					{data.partner.firstName}
-					{data.partner.lastName}'s photos
-				</p>
-			</svelte:fragment>
-		</ControlAppBar>
-	{/if}
-	<AssetGrid user={data.partner} />
+  {#if $isMultiSelectStoreState}
+    <AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
+      <DownloadAction />
+    </AssetSelectControlBar>
+    <AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
+      <CreateSharedLink />
+      <AssetSelectContextMenu icon={Plus} title="Add">
+        <AddToAlbum />
+        <AddToAlbum shared />
+      </AssetSelectContextMenu>
+      <DownloadAction />
+    </AssetSelectControlBar>
+  {:else}
+    <ControlAppBar showBackButton backIcon={ArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}>
+      <svelte:fragment slot="leading">
+        <p class="text-immich-fg dark:text-immich-dark-fg">
+          {data.partner.firstName}
+          {data.partner.lastName}'s photos
+        </p>
+      </svelte:fragment>
+    </ControlAppBar>
+  {/if}
+  <AssetGrid user={data.partner} />
 </main>
diff --git a/web/src/routes/(user)/people/+page.server.ts b/web/src/routes/(user)/people/+page.server.ts
index 07ce852f60..bfcae137d9 100644
--- a/web/src/routes/(user)/people/+page.server.ts
+++ b/web/src/routes/(user)/people/+page.server.ts
@@ -1,20 +1,20 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ locals, parent }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const { data: people } = await locals.api.personApi.getAllPeople();
+  const { data: people } = await locals.api.personApi.getAllPeople();
 
-	return {
-		user,
-		people,
-		meta: {
-			title: 'People'
-		}
-	};
+  return {
+    user,
+    people,
+    meta: {
+      title: 'People',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte
index 1f83d9d46e..b0f60a6d2a 100644
--- a/web/src/routes/(user)/people/+page.svelte
+++ b/web/src/routes/(user)/people/+page.svelte
@@ -1,48 +1,46 @@
 <script lang="ts">
-	import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import { api } from '@api';
-	import AccountOff from 'svelte-material-icons/AccountOff.svelte';
-	import type { PageData } from './$types';
+  import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import { api } from '@api';
+  import AccountOff from 'svelte-material-icons/AccountOff.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 </script>
 
 <UserPageLayout user={data.user} showUploadButton title="People">
-	{#if data.people.length > 0}
-		<div class="pl-4">
-			<div class="flex flex-row flex-wrap gap-1">
-				{#each data.people as person (person.id)}
-					<div class="relative">
-						<a href="/people/{person.id}" draggable="false">
-							<div class="filter brightness-75 rounded-xl w-48">
-								<ImageThumbnail
-									shadow
-									url={api.getPeopleThumbnailUrl(person.id)}
-									altText={person.name}
-									widthStyle="100%"
-								/>
-							</div>
-							{#if person.name}
-								<span
-									class="absolute bottom-2 w-full text-center font-medium text-white text-ellipsis w-100 px-1 hover:cursor-pointer backdrop-blur-[1px]"
-								>
-									{person.name}
-								</span>
-							{/if}
-						</a>
-					</div>
-				{/each}
-			</div>
-		</div>
-	{:else}
-		<div
-			class="flex items-center place-content-center w-full min-h-[calc(66vh_-_11rem)] dark:text-white"
-		>
-			<div class="flex flex-col content-center items-center text-center">
-				<AccountOff size="3.5em" />
-				<p class="font-medium text-3xl mt-5">No people</p>
-			</div>
-		</div>
-	{/if}
+  {#if data.people.length > 0}
+    <div class="pl-4">
+      <div class="flex flex-row flex-wrap gap-1">
+        {#each data.people as person (person.id)}
+          <div class="relative">
+            <a href="/people/{person.id}" draggable="false">
+              <div class="filter brightness-75 rounded-xl w-48">
+                <ImageThumbnail
+                  shadow
+                  url={api.getPeopleThumbnailUrl(person.id)}
+                  altText={person.name}
+                  widthStyle="100%"
+                />
+              </div>
+              {#if person.name}
+                <span
+                  class="absolute bottom-2 w-full text-center font-medium text-white text-ellipsis w-100 px-1 hover:cursor-pointer backdrop-blur-[1px]"
+                >
+                  {person.name}
+                </span>
+              {/if}
+            </a>
+          </div>
+        {/each}
+      </div>
+    </div>
+  {:else}
+    <div class="flex items-center place-content-center w-full min-h-[calc(66vh_-_11rem)] dark:text-white">
+      <div class="flex flex-col content-center items-center text-center">
+        <AccountOff size="3.5em" />
+        <p class="font-medium text-3xl mt-5">No people</p>
+      </div>
+    </div>
+  {/if}
 </UserPageLayout>
diff --git a/web/src/routes/(user)/people/[personId]/+page.server.ts b/web/src/routes/(user)/people/[personId]/+page.server.ts
index 27c3ac7976..564f4a0fda 100644
--- a/web/src/routes/(user)/people/[personId]/+page.server.ts
+++ b/web/src/routes/(user)/people/[personId]/+page.server.ts
@@ -1,22 +1,22 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ locals, parent, params }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const { data: person } = await locals.api.personApi.getPerson({ id: params.personId });
-	const { data: assets } = await locals.api.personApi.getPersonAssets({ id: params.personId });
+  const { data: person } = await locals.api.personApi.getPerson({ id: params.personId });
+  const { data: assets } = await locals.api.personApi.getPersonAssets({ id: params.personId });
 
-	return {
-		user,
-		assets,
-		person,
-		meta: {
-			title: person.name || 'Person'
-		}
-	};
+  return {
+    user,
+    assets,
+    person,
+    meta: {
+      title: person.name || 'Person',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/people/[personId]/+page.svelte b/web/src/routes/(user)/people/[personId]/+page.svelte
index 0724d2d15e..53523e4b8b 100644
--- a/web/src/routes/(user)/people/[personId]/+page.svelte
+++ b/web/src/routes/(user)/people/[personId]/+page.svelte
@@ -1,133 +1,118 @@
 <script lang="ts">
-	import { afterNavigate, goto } from '$app/navigation';
-	import { page } from '$app/stores';
-	import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
-	import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
-	import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
-	import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
-	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
-	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
-	import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
-	import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
-	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
-	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
-	import { AppRoute } from '$lib/constants';
-	import { handleError } from '$lib/utils/handle-error';
-	import { AssetResponseDto, api } from '@api';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import type { PageData } from './$types';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import SelectAll from 'svelte-material-icons/SelectAll.svelte';
+  import { afterNavigate, goto } from '$app/navigation';
+  import { page } from '$app/stores';
+  import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
+  import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
+  import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
+  import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
+  import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+  import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+  import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
+  import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
+  import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+  import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+  import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
+  import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
+  import { AppRoute } from '$lib/constants';
+  import { handleError } from '$lib/utils/handle-error';
+  import { AssetResponseDto, api } from '@api';
+  import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import type { PageData } from './$types';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import SelectAll from 'svelte-material-icons/SelectAll.svelte';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	let isEditName = false;
-	let previousRoute: string = AppRoute.EXPLORE;
-	let selectedAssets: Set<AssetResponseDto> = new Set();
-	$: isMultiSelectionMode = selectedAssets.size > 0;
-	$: isAllArchive = Array.from(selectedAssets).every((asset) => asset.isArchived);
-	$: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
+  let isEditName = false;
+  let previousRoute: string = AppRoute.EXPLORE;
+  let selectedAssets: Set<AssetResponseDto> = new Set();
+  $: isMultiSelectionMode = selectedAssets.size > 0;
+  $: isAllArchive = Array.from(selectedAssets).every((asset) => asset.isArchived);
+  $: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
 
-	afterNavigate(({ from }) => {
-		// Prevent setting previousRoute to the current page.
-		if (from && from.route.id !== $page.route.id) {
-			previousRoute = from.url.href;
-		}
-	});
+  afterNavigate(({ from }) => {
+    // Prevent setting previousRoute to the current page.
+    if (from && from.route.id !== $page.route.id) {
+      previousRoute = from.url.href;
+    }
+  });
 
-	const handleNameChange = async (name: string) => {
-		try {
-			isEditName = false;
-			data.person.name = name;
-			await api.personApi.updatePerson({ id: data.person.id, personUpdateDto: { name } });
-		} catch (error) {
-			handleError(error, 'Unable to save name');
-		}
-	};
+  const handleNameChange = async (name: string) => {
+    try {
+      isEditName = false;
+      data.person.name = name;
+      await api.personApi.updatePerson({ id: data.person.id, personUpdateDto: { name } });
+    } catch (error) {
+      handleError(error, 'Unable to save name');
+    }
+  };
 
-	const onAssetDelete = (assetId: string) => {
-		data.assets = data.assets.filter((asset: AssetResponseDto) => asset.id !== assetId);
-	};
-	const handleSelectAll = () => {
-		selectedAssets = new Set(data.assets);
-	};
+  const onAssetDelete = (assetId: string) => {
+    data.assets = data.assets.filter((asset: AssetResponseDto) => asset.id !== assetId);
+  };
+  const handleSelectAll = () => {
+    selectedAssets = new Set(data.assets);
+  };
 </script>
 
 {#if isMultiSelectionMode}
-	<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
-		<CreateSharedLink />
-		<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
-		<AssetSelectContextMenu icon={Plus} title="Add">
-			<AddToAlbum />
-			<AddToAlbum shared />
-		</AssetSelectContextMenu>
-		<DeleteAssets {onAssetDelete} />
-		<AssetSelectContextMenu icon={DotsVertical} title="Add">
-			<DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" />
-			<FavoriteAction menuItem removeFavorite={isAllFavorite} />
-			<ArchiveAction
-				menuItem
-				unarchive={isAllArchive}
-				onAssetArchive={(asset) => onAssetDelete(asset.id)}
-			/>
-		</AssetSelectContextMenu>
-	</AssetSelectControlBar>
+  <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
+    <CreateSharedLink />
+    <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
+    <AssetSelectContextMenu icon={Plus} title="Add">
+      <AddToAlbum />
+      <AddToAlbum shared />
+    </AssetSelectContextMenu>
+    <DeleteAssets {onAssetDelete} />
+    <AssetSelectContextMenu icon={DotsVertical} title="Add">
+      <DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" />
+      <FavoriteAction menuItem removeFavorite={isAllFavorite} />
+      <ArchiveAction menuItem unarchive={isAllArchive} onAssetArchive={(asset) => onAssetDelete(asset.id)} />
+    </AssetSelectContextMenu>
+  </AssetSelectControlBar>
 {:else}
-	<ControlAppBar
-		showBackButton
-		backIcon={ArrowLeft}
-		on:close-button-click={() => goto(previousRoute)}
-	/>
+  <ControlAppBar showBackButton backIcon={ArrowLeft} on:close-button-click={() => goto(previousRoute)} />
 {/if}
 
 <!-- Face information block -->
 <section class="pt-24 px-4 sm:px-6 flex place-items-center">
-	{#if isEditName}
-		<EditNameInput
-			person={data.person}
-			on:change={(event) => handleNameChange(event.detail)}
-			on:cancel={() => (isEditName = false)}
-		/>
-	{:else}
-		<ImageThumbnail
-			circle
-			shadow
-			url={api.getPeopleThumbnailUrl(data.person.id)}
-			altText={data.person.name}
-			widthStyle="3.375rem"
-			heightStyle="3.375rem"
-		/>
-		<button
-			title="Edit name"
-			class="px-4 text-immich-primary dark:text-immich-dark-primary"
-			on:click={() => (isEditName = true)}
-		>
-			{#if data.person.name}
-				<p class="font-medium py-2">{data.person.name}</p>
-			{:else}
-				<p class="font-medium w-fit">Add a name</p>
-				<p class="text-sm text-gray-500 dark:text-immich-gray">
-					Find them fast by name with search
-				</p>
-			{/if}
-		</button>
-	{/if}
+  {#if isEditName}
+    <EditNameInput
+      person={data.person}
+      on:change={(event) => handleNameChange(event.detail)}
+      on:cancel={() => (isEditName = false)}
+    />
+  {:else}
+    <ImageThumbnail
+      circle
+      shadow
+      url={api.getPeopleThumbnailUrl(data.person.id)}
+      altText={data.person.name}
+      widthStyle="3.375rem"
+      heightStyle="3.375rem"
+    />
+    <button
+      title="Edit name"
+      class="px-4 text-immich-primary dark:text-immich-dark-primary"
+      on:click={() => (isEditName = true)}
+    >
+      {#if data.person.name}
+        <p class="font-medium py-2">{data.person.name}</p>
+      {:else}
+        <p class="font-medium w-fit">Add a name</p>
+        <p class="text-sm text-gray-500 dark:text-immich-gray">Find them fast by name with search</p>
+      {/if}
+    </button>
+  {/if}
 </section>
 
 <!-- Gallery Block -->
 <section class="relative pt-8 sm:px-4 mb-12 bg-immich-bg dark:bg-immich-dark-bg">
-	<section class="overflow-y-auto relative immich-scrollbar">
-		<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
-			<GalleryViewer
-				assets={data.assets}
-				viewFrom="search-page"
-				showArchiveIcon={true}
-				bind:selectedAssets
-			/>
-		</section>
-	</section>
+  <section class="overflow-y-auto relative immich-scrollbar">
+    <section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
+      <GalleryViewer assets={data.assets} viewFrom="search-page" showArchiveIcon={true} bind:selectedAssets />
+    </section>
+  </section>
 </section>
diff --git a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts
index 235e620d6c..ff1ae1cf8a 100644
--- a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts
+++ b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts
@@ -1,14 +1,14 @@
-import { redirect } from '@sveltejs/kit';
-export const prerender = false;
-import type { PageLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageLoad } from './$types';
+export const prerender = false;
 
 export const load: PageLoad = async ({ params, parent }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const personId = params['personId'];
-	throw redirect(302, `${AppRoute.PEOPLE}/${personId}`);
+  const personId = params['personId'];
+  throw redirect(302, `${AppRoute.PEOPLE}/${personId}`);
 };
diff --git a/web/src/routes/(user)/photos/+page.server.ts b/web/src/routes/(user)/photos/+page.server.ts
index db2e0b5391..e8965f02fc 100644
--- a/web/src/routes/(user)/photos/+page.server.ts
+++ b/web/src/routes/(user)/photos/+page.server.ts
@@ -1,16 +1,16 @@
-import type { PageServerLoad } from './$types';
-import { redirect } from '@sveltejs/kit';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Photos'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Photos',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte
index d3915f548a..830c21f7f2 100644
--- a/web/src/routes/(user)/photos/+page.svelte
+++ b/web/src/routes/(user)/photos/+page.svelte
@@ -1,57 +1,50 @@
 <script lang="ts">
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
-	import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
-	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
-	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
-	import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
-	import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
-	import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
-	import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
-	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
-	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
-	import {
-		assetInteractionStore,
-		isMultiSelectStoreState,
-		selectedAssets
-	} from '$lib/stores/asset-interaction.store';
-	import { assetStore } from '$lib/stores/assets.store';
-	import { onDestroy } from 'svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import type { PageData } from './$types';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
+  import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
+  import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+  import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+  import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
+  import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
+  import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
+  import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
+  import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+  import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+  import { assetInteractionStore, isMultiSelectStoreState, selectedAssets } from '$lib/stores/asset-interaction.store';
+  import { assetStore } from '$lib/stores/assets.store';
+  import { onDestroy } from 'svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	onDestroy(() => {
-		assetInteractionStore.clearMultiselect();
-	});
+  onDestroy(() => {
+    assetInteractionStore.clearMultiselect();
+  });
 
-	$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
+  $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
 </script>
 
 <UserPageLayout user={data.user} hideNavbar={$isMultiSelectStoreState} showUploadButton>
-	<svelte:fragment slot="header">
-		{#if $isMultiSelectStoreState}
-			<AssetSelectControlBar
-				assets={$selectedAssets}
-				clearSelect={assetInteractionStore.clearMultiselect}
-			>
-				<CreateSharedLink />
-				<SelectAllAssets />
-				<AssetSelectContextMenu icon={Plus} title="Add">
-					<AddToAlbum />
-					<AddToAlbum shared />
-				</AssetSelectContextMenu>
-				<DeleteAssets onAssetDelete={assetStore.removeAsset} />
-				<AssetSelectContextMenu icon={DotsVertical} title="Menu">
-					<FavoriteAction menuItem removeFavorite={isAllFavorite} />
-					<DownloadAction menuItem />
-					<ArchiveAction menuItem onAssetArchive={(asset) => assetStore.removeAsset(asset.id)} />
-				</AssetSelectContextMenu>
-			</AssetSelectControlBar>
-		{/if}
-	</svelte:fragment>
+  <svelte:fragment slot="header">
+    {#if $isMultiSelectStoreState}
+      <AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
+        <CreateSharedLink />
+        <SelectAllAssets />
+        <AssetSelectContextMenu icon={Plus} title="Add">
+          <AddToAlbum />
+          <AddToAlbum shared />
+        </AssetSelectContextMenu>
+        <DeleteAssets onAssetDelete={assetStore.removeAsset} />
+        <AssetSelectContextMenu icon={DotsVertical} title="Menu">
+          <FavoriteAction menuItem removeFavorite={isAllFavorite} />
+          <DownloadAction menuItem />
+          <ArchiveAction menuItem onAssetArchive={(asset) => assetStore.removeAsset(asset.id)} />
+        </AssetSelectContextMenu>
+      </AssetSelectControlBar>
+    {/if}
+  </svelte:fragment>
 
-	<AssetGrid slot="content" showMemoryLane />
+  <AssetGrid slot="content" showMemoryLane />
 </UserPageLayout>
diff --git a/web/src/routes/(user)/photos/[assetId]/+page.server.ts b/web/src/routes/(user)/photos/[assetId]/+page.server.ts
index a44ab35a04..1c8a2a51cd 100644
--- a/web/src/routes/(user)/photos/[assetId]/+page.server.ts
+++ b/web/src/routes/(user)/photos/[assetId]/+page.server.ts
@@ -1,15 +1,15 @@
 import { redirect } from '@sveltejs/kit';
 export const prerender = false;
 
-import type { PageServerLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import type { PageServerLoad } from './$types';
 
 export const load: PageServerLoad = async ({ parent }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 };
diff --git a/web/src/routes/(user)/search/+page.server.ts b/web/src/routes/(user)/search/+page.server.ts
index 14e03c970b..17a09fefc0 100644
--- a/web/src/routes/(user)/search/+page.server.ts
+++ b/web/src/routes/(user)/search/+page.server.ts
@@ -1,23 +1,23 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ locals, parent, url }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	const term = url.searchParams.get('q') || url.searchParams.get('query') || undefined;
+  const term = url.searchParams.get('q') || url.searchParams.get('query') || undefined;
 
-	const { data: results } = await locals.api.searchApi.search({}, { params: url.searchParams });
+  const { data: results } = await locals.api.searchApi.search({}, { params: url.searchParams });
 
-	return {
-		user,
-		term,
-		results,
-		meta: {
-			title: 'Search'
-		}
-	};
+  return {
+    user,
+    term,
+    results,
+    meta: {
+      title: 'Search',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/search/+page.svelte b/web/src/routes/(user)/search/+page.svelte
index 69bffadde0..07d8943d8e 100644
--- a/web/src/routes/(user)/search/+page.svelte
+++ b/web/src/routes/(user)/search/+page.svelte
@@ -1,144 +1,132 @@
 <script lang="ts">
-	import { afterNavigate, goto } from '$app/navigation';
-	import { page } from '$app/stores';
-	import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
-	import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
-	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
-	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
-	import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
-	import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
-	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
-	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
-	import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
-	import type { AssetResponseDto } from '@api';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import ImageOffOutline from 'svelte-material-icons/ImageOffOutline.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import type { PageData } from './$types';
-	import SelectAll from 'svelte-material-icons/SelectAll.svelte';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import { AppRoute } from '$lib/constants';
-	import AlbumCard from '$lib/components/album-page/album-card.svelte';
-	import { flip } from 'svelte/animate';
+  import { afterNavigate, goto } from '$app/navigation';
+  import { page } from '$app/stores';
+  import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
+  import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
+  import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+  import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+  import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
+  import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
+  import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+  import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+  import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
+  import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
+  import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
+  import type { AssetResponseDto } from '@api';
+  import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+  import ImageOffOutline from 'svelte-material-icons/ImageOffOutline.svelte';
+  import Plus from 'svelte-material-icons/Plus.svelte';
+  import type { PageData } from './$types';
+  import SelectAll from 'svelte-material-icons/SelectAll.svelte';
+  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+  import { AppRoute } from '$lib/constants';
+  import AlbumCard from '$lib/components/album-page/album-card.svelte';
+  import { flip } from 'svelte/animate';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	// The GalleryViewer pushes it's own history state, which causes weird
-	// behavior for history.back(). To prevent that we store the previous page
-	// manually and navigate back to that.
-	let previousRoute = AppRoute.EXPLORE as string;
-	$: albums = data.results.albums.items;
+  // The GalleryViewer pushes it's own history state, which causes weird
+  // behavior for history.back(). To prevent that we store the previous page
+  // manually and navigate back to that.
+  let previousRoute = AppRoute.EXPLORE as string;
+  $: albums = data.results.albums.items;
 
-	afterNavigate(({ from }) => {
-		// Prevent setting previousRoute to the current page.
-		if (from && from.route.id !== $page.route.id) {
-			previousRoute = from.url.href;
-		}
+  afterNavigate(({ from }) => {
+    // Prevent setting previousRoute to the current page.
+    if (from && from.route.id !== $page.route.id) {
+      previousRoute = from.url.href;
+    }
 
-		if (from?.route.id === '/(user)/albums/[albumId]') {
-			previousRoute = AppRoute.EXPLORE;
-		}
-	});
+    if (from?.route.id === '/(user)/albums/[albumId]') {
+      previousRoute = AppRoute.EXPLORE;
+    }
+  });
 
-	$: term = (() => {
-		let term = $page.url.searchParams.get('q') || data.term || '';
-		const isMetadataSearch = $page.url.searchParams.get('clip') === 'false';
-		if (isMetadataSearch && term !== '') {
-			term = `m:${term}`;
-		}
-		return term;
-	})();
+  $: term = (() => {
+    let term = $page.url.searchParams.get('q') || data.term || '';
+    const isMetadataSearch = $page.url.searchParams.get('clip') === 'false';
+    if (isMetadataSearch && term !== '') {
+      term = `m:${term}`;
+    }
+    return term;
+  })();
 
-	let selectedAssets: Set<AssetResponseDto> = new Set();
-	$: isMultiSelectionMode = selectedAssets.size > 0;
-	$: isAllArchived = Array.from(selectedAssets).every((asset) => asset.isArchived);
-	$: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
-	$: searchResultAssets = data.results.assets.items;
+  let selectedAssets: Set<AssetResponseDto> = new Set();
+  $: isMultiSelectionMode = selectedAssets.size > 0;
+  $: isAllArchived = Array.from(selectedAssets).every((asset) => asset.isArchived);
+  $: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
+  $: searchResultAssets = data.results.assets.items;
 
-	const onAssetDelete = (assetId: string) => {
-		searchResultAssets = searchResultAssets.filter((a: AssetResponseDto) => a.id !== assetId);
-	};
-	const handleSelectAll = () => {
-		selectedAssets = new Set(searchResultAssets);
-	};
+  const onAssetDelete = (assetId: string) => {
+    searchResultAssets = searchResultAssets.filter((a: AssetResponseDto) => a.id !== assetId);
+  };
+  const handleSelectAll = () => {
+    selectedAssets = new Set(searchResultAssets);
+  };
 </script>
 
 <section>
-	{#if isMultiSelectionMode}
-		<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
-			<CreateSharedLink />
-			<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
-			<AssetSelectContextMenu icon={Plus} title="Add">
-				<AddToAlbum />
-				<AddToAlbum shared />
-			</AssetSelectContextMenu>
-			<DeleteAssets {onAssetDelete} />
+  {#if isMultiSelectionMode}
+    <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
+      <CreateSharedLink />
+      <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
+      <AssetSelectContextMenu icon={Plus} title="Add">
+        <AddToAlbum />
+        <AddToAlbum shared />
+      </AssetSelectContextMenu>
+      <DeleteAssets {onAssetDelete} />
 
-			<AssetSelectContextMenu icon={DotsVertical} title="Add">
-				<DownloadAction menuItem />
-				<FavoriteAction menuItem removeFavorite={isAllFavorite} />
-				<ArchiveAction menuItem unarchive={isAllArchived} />
-			</AssetSelectContextMenu>
-		</AssetSelectControlBar>
-	{:else}
-		<ControlAppBar on:close-button-click={() => goto(previousRoute)} backIcon={ArrowLeft}>
-			<div class="w-full max-w-2xl flex-1 pl-4">
-				<SearchBar grayTheme={false} value={term} />
-			</div>
-		</ControlAppBar>
-	{/if}
+      <AssetSelectContextMenu icon={DotsVertical} title="Add">
+        <DownloadAction menuItem />
+        <FavoriteAction menuItem removeFavorite={isAllFavorite} />
+        <ArchiveAction menuItem unarchive={isAllArchived} />
+      </AssetSelectContextMenu>
+    </AssetSelectControlBar>
+  {:else}
+    <ControlAppBar on:close-button-click={() => goto(previousRoute)} backIcon={ArrowLeft}>
+      <div class="w-full max-w-2xl flex-1 pl-4">
+        <SearchBar grayTheme={false} value={term} />
+      </div>
+    </ControlAppBar>
+  {/if}
 </section>
 
 <section class="relative pt-32 mb-12 bg-immich-bg dark:bg-immich-dark-bg">
-	<section class="overflow-y-auto relative immich-scrollbar">
-		{#if albums.length}
-			<section>
-				<div class="text-4xl font-medium text-black/70 dark:text-white/80 ml-6">ALBUMS</div>
-				<div class="grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]">
-					{#each albums as album (album.id)}
-						<a
-							data-sveltekit-preload-data="hover"
-							href={`albums/${album.id}`}
-							animate:flip={{ duration: 200 }}
-						>
-							<AlbumCard
-								{album}
-								user={data.user}
-								isSharingView={false}
-								showItemCount={false}
-								showContextMenu={false}
-							/>
-						</a>
-					{/each}
-				</div>
+  <section class="overflow-y-auto relative immich-scrollbar">
+    {#if albums.length}
+      <section>
+        <div class="text-4xl font-medium text-black/70 dark:text-white/80 ml-6">ALBUMS</div>
+        <div class="grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]">
+          {#each albums as album (album.id)}
+            <a data-sveltekit-preload-data="hover" href={`albums/${album.id}`} animate:flip={{ duration: 200 }}>
+              <AlbumCard {album} user={data.user} isSharingView={false} showItemCount={false} showContextMenu={false} />
+            </a>
+          {/each}
+        </div>
 
-				<div class="m-6 text-4xl font-medium text-black/70 dark:text-white/80">PHOTOS & VIDEOS</div>
-			</section>
-		{/if}
-		<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
-			{#if data.results?.assets?.items.length > 0}
-				<div class="pl-4">
-					<GalleryViewer
-						assets={searchResultAssets}
-						bind:selectedAssets
-						viewFrom="search-page"
-						showArchiveIcon={true}
-					/>
-				</div>
-			{:else}
-				<div
-					class="flex items-center place-content-center w-full min-h-[calc(66vh_-_11rem)] dark:text-white"
-				>
-					<div class="flex flex-col content-center items-center text-center">
-						<ImageOffOutline size="3.5em" />
-						<p class="font-medium text-3xl mt-5">No results</p>
-						<p class="text-base font-normal">Try a synonym or more general keyword</p>
-					</div>
-				</div>
-			{/if}
-		</section>
-	</section>
+        <div class="m-6 text-4xl font-medium text-black/70 dark:text-white/80">PHOTOS & VIDEOS</div>
+      </section>
+    {/if}
+    <section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
+      {#if data.results?.assets?.items.length > 0}
+        <div class="pl-4">
+          <GalleryViewer
+            assets={searchResultAssets}
+            bind:selectedAssets
+            viewFrom="search-page"
+            showArchiveIcon={true}
+          />
+        </div>
+      {:else}
+        <div class="flex items-center place-content-center w-full min-h-[calc(66vh_-_11rem)] dark:text-white">
+          <div class="flex flex-col content-center items-center text-center">
+            <ImageOffOutline size="3.5em" />
+            <p class="font-medium text-3xl mt-5">No results</p>
+            <p class="text-base font-normal">Try a synonym or more general keyword</p>
+          </div>
+        </div>
+      {/if}
+    </section>
+  </section>
 </section>
diff --git a/web/src/routes/(user)/search/photos/[assetId]/+page.ts b/web/src/routes/(user)/search/photos/[assetId]/+page.ts
index 3b3b2caf0b..994670a69e 100644
--- a/web/src/routes/(user)/search/photos/[assetId]/+page.ts
+++ b/web/src/routes/(user)/search/photos/[assetId]/+page.ts
@@ -1,13 +1,13 @@
-import { redirect } from '@sveltejs/kit';
-export const prerender = false;
-import type { PageLoad } from './$types';
 import { AppRoute } from '$lib/constants';
+import { redirect } from '@sveltejs/kit';
+import type { PageLoad } from './$types';
+export const prerender = false;
 
 export const load: PageLoad = async ({ parent }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	throw redirect(302, AppRoute.SEARCH);
+  throw redirect(302, AppRoute.SEARCH);
 };
diff --git a/web/src/routes/(user)/share/[key]/+error.svelte b/web/src/routes/(user)/share/[key]/+error.svelte
index 966bb1b15a..2d85595fdd 100644
--- a/web/src/routes/(user)/share/[key]/+error.svelte
+++ b/web/src/routes/(user)/share/[key]/+error.svelte
@@ -1,9 +1,7 @@
 <svelte:head>
-	<title>Opps! Error - Immich</title>
+  <title>Opps! Error - Immich</title>
 </svelte:head>
 
 <section class="w-screen h-screen flex place-items-center place-content-center">
-	<div class="p-20 text-4xl dark:text-immich-dark-primary text-immich-primary">
-		Page not found :/
-	</div>
+  <div class="p-20 text-4xl dark:text-immich-dark-primary text-immich-primary">Page not found :/</div>
 </section>
diff --git a/web/src/routes/(user)/share/[key]/+page.server.ts b/web/src/routes/(user)/share/[key]/+page.server.ts
index c2863f093f..d1d711fda2 100644
--- a/web/src/routes/(user)/share/[key]/+page.server.ts
+++ b/web/src/routes/(user)/share/[key]/+page.server.ts
@@ -1,30 +1,30 @@
-import { error } from '@sveltejs/kit';
-import { ThumbnailFormat, api as clientApi } from '@api';
-import type { PageServerLoad } from './$types';
 import featurePanelUrl from '$lib/assets/feature-panel.png';
+import { api as clientApi, ThumbnailFormat } from '@api';
+import { error } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
 
 export const load = (async ({ params, locals: { api } }) => {
-	const { key } = params;
+  const { key } = params;
 
-	try {
-		const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key });
+  try {
+    const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key });
 
-		const assetCount = sharedLink.assets.length;
-		const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
+    const assetCount = sharedLink.assets.length;
+    const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
 
-		return {
-			sharedLink,
-			meta: {
-				title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
-				description: sharedLink.description || `${assetCount} shared photos & videos.`,
-				imageUrl: assetId
-					? clientApi.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
-					: featurePanelUrl
-			}
-		};
-	} catch (e) {
-		throw error(404, {
-			message: 'Invalid shared link'
-		});
-	}
+    return {
+      sharedLink,
+      meta: {
+        title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
+        description: sharedLink.description || `${assetCount} shared photos & videos.`,
+        imageUrl: assetId
+          ? clientApi.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
+          : featurePanelUrl,
+      },
+    };
+  } catch (e) {
+    throw error(404, {
+      message: 'Invalid shared link',
+    });
+  }
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/share/[key]/+page.svelte b/web/src/routes/(user)/share/[key]/+page.svelte
index 258fd71204..bece11c329 100644
--- a/web/src/routes/(user)/share/[key]/+page.svelte
+++ b/web/src/routes/(user)/share/[key]/+page.svelte
@@ -1,27 +1,27 @@
 <script lang="ts">
-	import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
-	import IndividualSharedViewer from '$lib/components/share-page/individual-shared-viewer.svelte';
-	import { AlbumResponseDto, SharedLinkType } from '@api';
-	import type { PageData } from './$types';
+  import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
+  import IndividualSharedViewer from '$lib/components/share-page/individual-shared-viewer.svelte';
+  import { AlbumResponseDto, SharedLinkType } from '@api';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
-	const { sharedLink } = data;
+  export let data: PageData;
+  const { sharedLink } = data;
 
-	let album: AlbumResponseDto | null = null;
-	let isOwned = data.user ? data.user.id === sharedLink.userId : false;
-	if (sharedLink.album) {
-		album = { ...sharedLink.album, assets: sharedLink.assets };
-	}
+  let album: AlbumResponseDto | null = null;
+  let isOwned = data.user ? data.user.id === sharedLink.userId : false;
+  if (sharedLink.album) {
+    album = { ...sharedLink.album, assets: sharedLink.assets };
+  }
 </script>
 
 {#if sharedLink.type == SharedLinkType.Album && album}
-	<div class="immich-scrollbar">
-		<AlbumViewer {album} {sharedLink} />
-	</div>
+  <div class="immich-scrollbar">
+    <AlbumViewer {album} {sharedLink} />
+  </div>
 {/if}
 
 {#if sharedLink.type == SharedLinkType.Individual}
-	<div class="immich-scrollbar">
-		<IndividualSharedViewer {sharedLink} {isOwned} />
-	</div>
+  <div class="immich-scrollbar">
+    <IndividualSharedViewer {sharedLink} {isOwned} />
+  </div>
 {/if}
diff --git a/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.server.ts b/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.server.ts
index af5e6b6528..b0e26e8426 100644
--- a/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.server.ts
+++ b/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.server.ts
@@ -2,18 +2,18 @@ import { error } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ params, locals: { api } }) => {
-	const { key, assetId } = params;
-	const { data: asset } = await api.assetApi.getAssetById({ id: assetId, key });
+  const { key, assetId } = params;
+  const { data: asset } = await api.assetApi.getAssetById({ id: assetId, key });
 
-	if (!asset) {
-		throw error(404, 'Asset not found');
-	}
+  if (!asset) {
+    throw error(404, 'Asset not found');
+  }
 
-	return {
-		asset,
-		key,
-		meta: {
-			title: 'Public Share'
-		}
-	};
+  return {
+    asset,
+    key,
+    meta: {
+      title: 'Public Share',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.svelte b/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.svelte
index 41f5012c9d..800f1f6fd2 100644
--- a/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.svelte
+++ b/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.svelte
@@ -1,17 +1,17 @@
 <script lang="ts">
-	import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
-	import type { PageData } from './$types';
-	import { goto } from '$app/navigation';
-	export let data: PageData;
+  import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
+  import type { PageData } from './$types';
+  import { goto } from '$app/navigation';
+  export let data: PageData;
 </script>
 
 {#if data.asset && data.key}
-	<AssetViewer
-		asset={data.asset}
-		publicSharedKey={data.key}
-		on:navigate-previous={() => null}
-		on:navigate-next={() => null}
-		showNavigation={false}
-		on:close={() => goto(`/share/${data.key}`)}
-	/>
+  <AssetViewer
+    asset={data.asset}
+    publicSharedKey={data.key}
+    on:navigate-previous={() => null}
+    on:navigate-next={() => null}
+    showNavigation={false}
+    on:close={() => goto(`/share/${data.key}`)}
+  />
 {/if}
diff --git a/web/src/routes/(user)/sharing/+page.server.ts b/web/src/routes/(user)/sharing/+page.server.ts
index 4e8d4f764a..2b77b4677a 100644
--- a/web/src/routes/(user)/sharing/+page.server.ts
+++ b/web/src/routes/(user)/sharing/+page.server.ts
@@ -3,24 +3,24 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { api, user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	try {
-		const { data: sharedAlbums } = await api.albumApi.getAllAlbums({ shared: true });
-		const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-with' });
+  try {
+    const { data: sharedAlbums } = await api.albumApi.getAllAlbums({ shared: true });
+    const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-with' });
 
-		return {
-			user,
-			sharedAlbums,
-			partners,
-			meta: {
-				title: 'Sharing'
-			}
-		};
-	} catch (e) {
-		console.log(e);
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+    return {
+      user,
+      sharedAlbums,
+      partners,
+      meta: {
+        title: 'Sharing',
+      },
+    };
+  } catch (e) {
+    console.log(e);
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/sharing/+page.svelte b/web/src/routes/(user)/sharing/+page.svelte
index a2327d271c..3b967995a8 100644
--- a/web/src/routes/(user)/sharing/+page.svelte
+++ b/web/src/routes/(user)/sharing/+page.svelte
@@ -1,122 +1,118 @@
 <script lang="ts">
-	import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
-	import Link from 'svelte-material-icons/Link.svelte';
-	import { goto } from '$app/navigation';
-	import { api } from '@api';
-	import type { PageData } from './$types';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import empty2Url from '$lib/assets/empty-2.svg';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
-	import { flip } from 'svelte/animate';
-	import AlbumCard from '$lib/components/album-page/album-card.svelte';
-	import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
-	import { AppRoute } from '$lib/constants';
+  import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
+  import Link from 'svelte-material-icons/Link.svelte';
+  import { goto } from '$app/navigation';
+  import { api } from '@api';
+  import type { PageData } from './$types';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import empty2Url from '$lib/assets/empty-2.svg';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
+  import { flip } from 'svelte/animate';
+  import AlbumCard from '$lib/components/album-page/album-card.svelte';
+  import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
+  import { AppRoute } from '$lib/constants';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	const createSharedAlbum = async () => {
-		try {
-			const { data: newAlbum } = await api.albumApi.createAlbum({
-				createAlbumDto: {
-					albumName: 'Untitled'
-				}
-			});
+  const createSharedAlbum = async () => {
+    try {
+      const { data: newAlbum } = await api.albumApi.createAlbum({
+        createAlbumDto: {
+          albumName: 'Untitled',
+        },
+      });
 
-			goto('/albums/' + newAlbum.id);
-		} catch (e) {
-			notificationController.show({
-				message: 'Error creating album, check console for more details',
-				type: NotificationType.Error
-			});
+      goto('/albums/' + newAlbum.id);
+    } catch (e) {
+      notificationController.show({
+        message: 'Error creating album, check console for more details',
+        type: NotificationType.Error,
+      });
 
-			console.log('Error [createAlbum] ', e);
-		}
-	};
+      console.log('Error [createAlbum] ', e);
+    }
+  };
 </script>
 
 <UserPageLayout user={data.user} title={data.meta.title}>
-	<div class="flex" slot="buttons">
-		<LinkButton on:click={createSharedAlbum}>
-			<div class="flex place-items-center gap-x-1 text-sm flex-wrap justify-center">
-				<PlusBoxOutline size="18" class="shrink-0" />
-				<span class="max-sm:text-xs leading-none">Create shared album</span>
-			</div>
-		</LinkButton>
+  <div class="flex" slot="buttons">
+    <LinkButton on:click={createSharedAlbum}>
+      <div class="flex place-items-center gap-x-1 text-sm flex-wrap justify-center">
+        <PlusBoxOutline size="18" class="shrink-0" />
+        <span class="max-sm:text-xs leading-none">Create shared album</span>
+      </div>
+    </LinkButton>
 
-		<LinkButton on:click={() => goto(AppRoute.SHARED_LINKS)}>
-			<div class="flex place-items-center gap-x-1 text-sm flex-wrap justify-center">
-				<Link size="18" class="shrink-0" />
-				<span class="max-sm:text-xs leading-none">Shared links</span>
-			</div>
-		</LinkButton>
-	</div>
+    <LinkButton on:click={() => goto(AppRoute.SHARED_LINKS)}>
+      <div class="flex place-items-center gap-x-1 text-sm flex-wrap justify-center">
+        <Link size="18" class="shrink-0" />
+        <span class="max-sm:text-xs leading-none">Shared links</span>
+      </div>
+    </LinkButton>
+  </div>
 
-	<div class="flex flex-col">
-		{#if data.partners.length > 0}
-			<div class="mb-6 mt-2">
-				<div>
-					<p class="mb-4 dark:text-immich-dark-fg font-medium">Partners</p>
-				</div>
+  <div class="flex flex-col">
+    {#if data.partners.length > 0}
+      <div class="mb-6 mt-2">
+        <div>
+          <p class="mb-4 dark:text-immich-dark-fg font-medium">Partners</p>
+        </div>
 
-				<div class="flex flex-row flex-wrap gap-4">
-					{#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"
-						>
-							<UserAvatar user={partner} size="md" autoColor />
-							<div class="text-left">
-								<p class="text-immich-fg dark:text-immich-dark-fg">
-									{partner.firstName}
-									{partner.lastName}
-								</p>
-								<p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
-									{partner.email}
-								</p>
-							</div>
-						</a>
-					{/each}
-				</div>
-			</div>
+        <div class="flex flex-row flex-wrap gap-4">
+          {#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"
+            >
+              <UserAvatar user={partner} size="md" autoColor />
+              <div class="text-left">
+                <p class="text-immich-fg dark:text-immich-dark-fg">
+                  {partner.firstName}
+                  {partner.lastName}
+                </p>
+                <p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
+                  {partner.email}
+                </p>
+              </div>
+            </a>
+          {/each}
+        </div>
+      </div>
 
-			<hr class="dark:border-immich-dark-gray mb-4" />
-		{/if}
+      <hr class="dark:border-immich-dark-gray mb-4" />
+    {/if}
 
-		<div class="mb-6 mt-2">
-			<div>
-				<p class="mb-4 dark:text-immich-dark-fg font-medium">Albums</p>
-			</div>
+    <div class="mb-6 mt-2">
+      <div>
+        <p class="mb-4 dark:text-immich-dark-fg font-medium">Albums</p>
+      </div>
 
-			<div>
-				<!-- Share Album List -->
-				<div class="grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]">
-					{#each data.sharedAlbums as album (album.id)}
-						<a
-							data-sveltekit-preload-data="hover"
-							href={`albums/${album.id}`}
-							animate:flip={{ duration: 200 }}
-						>
-							<AlbumCard {album} user={data.user} isSharingView showContextMenu={false} />
-						</a>
-					{/each}
-				</div>
+      <div>
+        <!-- Share Album List -->
+        <div class="grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]">
+          {#each data.sharedAlbums as album (album.id)}
+            <a data-sveltekit-preload-data="hover" href={`albums/${album.id}`} animate:flip={{ duration: 200 }}>
+              <AlbumCard {album} user={data.user} isSharingView showContextMenu={false} />
+            </a>
+          {/each}
+        </div>
 
-				<!-- Empty List -->
-				{#if data.sharedAlbums.length === 0}
-					<div
-						class="border dark:border-immich-dark-gray p-5 md:w-[500px] w-2/3 m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center dark:text-immich-dark-fg"
-					>
-						<img src={empty2Url} alt="Empty shared album" width="500" draggable="false" />
-						<p class="text-center text-immich-text-gray-500">
-							Create a shared album to share photos and videos with people in your network
-						</p>
-					</div>
-				{/if}
-			</div>
-		</div>
-	</div>
+        <!-- Empty List -->
+        {#if data.sharedAlbums.length === 0}
+          <div
+            class="border dark:border-immich-dark-gray p-5 md:w-[500px] w-2/3 m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center dark:text-immich-dark-fg"
+          >
+            <img src={empty2Url} alt="Empty shared album" width="500" draggable="false" />
+            <p class="text-center text-immich-text-gray-500">
+              Create a shared album to share photos and videos with people in your network
+            </p>
+          </div>
+        {/if}
+      </div>
+    </div>
+  </div>
 </UserPageLayout>
diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.server.ts b/web/src/routes/(user)/sharing/sharedlinks/+page.server.ts
index ec3d8ee664..c849ff997d 100644
--- a/web/src/routes/(user)/sharing/sharedlinks/+page.server.ts
+++ b/web/src/routes/(user)/sharing/sharedlinks/+page.server.ts
@@ -3,14 +3,14 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Shared Links'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Shared Links',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte
index 6cd7e2e51a..04f77683c4 100644
--- a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte
+++ b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte
@@ -1,106 +1,104 @@
 <script lang="ts">
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import { api, SharedLinkResponseDto } from '@api';
-	import { goto } from '$app/navigation';
-	import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { onMount } from 'svelte';
-	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
-	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
-	import { handleError } from '$lib/utils/handle-error';
-	import { AppRoute } from '$lib/constants';
+  import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
+  import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+  import { api, SharedLinkResponseDto } from '@api';
+  import { goto } from '$app/navigation';
+  import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { onMount } from 'svelte';
+  import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
+  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
+  import { handleError } from '$lib/utils/handle-error';
+  import { AppRoute } from '$lib/constants';
 
-	let sharedLinks: SharedLinkResponseDto[] = [];
-	let editSharedLink: SharedLinkResponseDto | null = null;
+  let sharedLinks: SharedLinkResponseDto[] = [];
+  let editSharedLink: SharedLinkResponseDto | null = null;
 
-	let deleteLinkId: string | null = null;
+  let deleteLinkId: string | null = null;
 
-	const refresh = async () => {
-		const { data } = await api.sharedLinkApi.getAllSharedLinks();
-		sharedLinks = data;
-	};
+  const refresh = async () => {
+    const { data } = await api.sharedLinkApi.getAllSharedLinks();
+    sharedLinks = data;
+  };
 
-	onMount(async () => {
-		await refresh();
-	});
+  onMount(async () => {
+    await refresh();
+  });
 
-	const handleDeleteLink = async () => {
-		if (!deleteLinkId) {
-			return;
-		}
+  const handleDeleteLink = async () => {
+    if (!deleteLinkId) {
+      return;
+    }
 
-		try {
-			await api.sharedLinkApi.removeSharedLink({ id: deleteLinkId });
-			notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
-			deleteLinkId = null;
-			refresh();
-		} catch (error) {
-			handleError(error, 'Unable to delete shared link');
-		}
-	};
+    try {
+      await api.sharedLinkApi.removeSharedLink({ id: deleteLinkId });
+      notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
+      deleteLinkId = null;
+      refresh();
+    } catch (error) {
+      handleError(error, 'Unable to delete shared link');
+    }
+  };
 
-	const handleEditDone = async () => {
-		refresh();
-		editSharedLink = null;
-	};
+  const handleEditDone = async () => {
+    refresh();
+    editSharedLink = null;
+  };
 
-	const handleCopyLink = async (key: string) => {
-		const link = `${window.location.origin}/share/${key}`;
-		await navigator.clipboard.writeText(link);
-		notificationController.show({
-			message: 'Link copied to clipboard',
-			type: NotificationType.Info
-		});
-	};
+  const handleCopyLink = async (key: string) => {
+    const link = `${window.location.origin}/share/${key}`;
+    await navigator.clipboard.writeText(link);
+    notificationController.show({
+      message: 'Link copied to clipboard',
+      type: NotificationType.Info,
+    });
+  };
 </script>
 
 <ControlAppBar backIcon={ArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}>
-	<svelte:fragment slot="leading">Shared links</svelte:fragment>
+  <svelte:fragment slot="leading">Shared links</svelte:fragment>
 </ControlAppBar>
 
 <section class="flex flex-col pb-[120px] mt-[120px]">
-	<div class="w-[50%] m-auto mb-4 dark:text-immich-gray">
-		<p>Manage shared links</p>
-	</div>
-	{#if sharedLinks.length === 0}
-		<div
-			class="w-[50%] m-auto bg-gray-100 flex place-items-center place-content-center rounded-lg p-12"
-		>
-			<p>You don't have any shared links</p>
-		</div>
-	{:else}
-		<div class="flex flex-col w-[50%] m-auto">
-			{#each sharedLinks as link (link.id)}
-				<SharedLinkCard
-					{link}
-					on:delete={() => (deleteLinkId = link.id)}
-					on:edit={() => (editSharedLink = link)}
-					on:copy={() => handleCopyLink(link.key)}
-				/>
-			{/each}
-		</div>
-	{/if}
+  <div class="w-[50%] m-auto mb-4 dark:text-immich-gray">
+    <p>Manage shared links</p>
+  </div>
+  {#if sharedLinks.length === 0}
+    <div class="w-[50%] m-auto bg-gray-100 flex place-items-center place-content-center rounded-lg p-12">
+      <p>You don't have any shared links</p>
+    </div>
+  {:else}
+    <div class="flex flex-col w-[50%] m-auto">
+      {#each sharedLinks as link (link.id)}
+        <SharedLinkCard
+          {link}
+          on:delete={() => (deleteLinkId = link.id)}
+          on:edit={() => (editSharedLink = link)}
+          on:copy={() => handleCopyLink(link.key)}
+        />
+      {/each}
+    </div>
+  {/if}
 </section>
 
 {#if editSharedLink}
-	<CreateSharedLinkModal
-		editingLink={editSharedLink}
-		shareType={editSharedLink.type}
-		album={editSharedLink.album}
-		on:close={handleEditDone}
-	/>
+  <CreateSharedLinkModal
+    editingLink={editSharedLink}
+    shareType={editSharedLink.type}
+    album={editSharedLink.album}
+    on:close={handleEditDone}
+  />
 {/if}
 
 {#if deleteLinkId}
-	<ConfirmDialogue
-		title="Delete Shared Link"
-		prompt="Are you sure you want to delete this shared link?"
-		confirmText="Delete"
-		on:confirm={() => handleDeleteLink()}
-		on:cancel={() => (deleteLinkId = null)}
-	/>
+  <ConfirmDialogue
+    title="Delete Shared Link"
+    prompt="Are you sure you want to delete this shared link?"
+    confirmText="Delete"
+    on:confirm={() => handleDeleteLink()}
+    on:cancel={() => (deleteLinkId = null)}
+  />
 {/if}
diff --git a/web/src/routes/(user)/user-settings/+page.server.ts b/web/src/routes/(user)/user-settings/+page.server.ts
index 80784c7b84..c952d6cb53 100644
--- a/web/src/routes/(user)/user-settings/+page.server.ts
+++ b/web/src/routes/(user)/user-settings/+page.server.ts
@@ -3,14 +3,14 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Settings'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Settings',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/(user)/user-settings/+page.svelte b/web/src/routes/(user)/user-settings/+page.svelte
index 0cc87046f2..aac45c93af 100644
--- a/web/src/routes/(user)/user-settings/+page.svelte
+++ b/web/src/routes/(user)/user-settings/+page.svelte
@@ -1,15 +1,15 @@
 <script lang="ts">
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import UserSettingsList from '$lib/components/user-settings-page/user-settings-list.svelte';
-	import type { PageData } from './$types';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import UserSettingsList from '$lib/components/user-settings-page/user-settings-list.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 </script>
 
 <UserPageLayout user={data.user} title={data.meta.title}>
-	<section class="flex place-content-center mx-4">
-		<div class="w-full max-w-3xl">
-			<UserSettingsList user={data.user} />
-		</div>
-	</section>
+  <section class="flex place-content-center mx-4">
+    <div class="w-full max-w-3xl">
+      <UserSettingsList user={data.user} />
+    </div>
+  </section>
 </UserPageLayout>
diff --git a/web/src/routes/+error.svelte b/web/src/routes/+error.svelte
index 42a22e9d8c..4702ca2d1a 100644
--- a/web/src/routes/+error.svelte
+++ b/web/src/routes/+error.svelte
@@ -1,123 +1,119 @@
 <script>
-	import { page } from '$app/stores';
-	import Message from 'svelte-material-icons/Message.svelte';
-	import PartyPopper from 'svelte-material-icons/PartyPopper.svelte';
-	import CodeTags from 'svelte-material-icons/CodeTags.svelte';
-	import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { handleError } from '$lib/utils/handle-error';
-	import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
+  import { page } from '$app/stores';
+  import Message from 'svelte-material-icons/Message.svelte';
+  import PartyPopper from 'svelte-material-icons/PartyPopper.svelte';
+  import CodeTags from 'svelte-material-icons/CodeTags.svelte';
+  import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
+  import {
+    notificationController,
+    NotificationType,
+  } from '$lib/components/shared-components/notification/notification';
+  import { handleError } from '$lib/utils/handle-error';
+  import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
 
-	const handleCopy = async () => {
-		//
-		const error = $page.error || null;
-		if (!error) {
-			return;
-		}
+  const handleCopy = async () => {
+    //
+    const error = $page.error || null;
+    if (!error) {
+      return;
+    }
 
-		try {
-			await navigator.clipboard.writeText(`${error.message} - ${error.code}\n${error.stack}`);
-			notificationController.show({
-				type: NotificationType.Info,
-				message: 'Copied error to clipboard'
-			});
-		} catch (error) {
-			handleError(error, 'Unable to copy to clipboard');
-		}
-	};
+    try {
+      await navigator.clipboard.writeText(`${error.message} - ${error.code}\n${error.stack}`);
+      notificationController.show({
+        type: NotificationType.Info,
+        message: 'Copied error to clipboard',
+      });
+    } catch (error) {
+      handleError(error, 'Unable to copy to clipboard');
+    }
+  };
 </script>
 
 <div class="h-screen w-screen">
-	<section class="bg-immich-bg dark:bg-immich-dark-bg">
-		<div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-4">
-			<a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
-				<ImmichLogo height="35" width="35" />
-				<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
-					IMMICH
-				</h1>
-			</a>
-		</div>
-	</section>
+  <section class="bg-immich-bg dark:bg-immich-dark-bg">
+    <div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-4">
+      <a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
+        <ImmichLogo height="35" width="35" />
+        <h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">IMMICH</h1>
+      </a>
+    </div>
+  </section>
 
-	<div
-		class="fixed top-0 w-full h-full bg-black/50 flex place-items-center place-content-center overflow-hidden"
-	>
-		<div>
-			<div
-				class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray shadow-sm w-[500px] max-w-[95vw] rounded-3xl dark:text-immich-dark-fg"
-			>
-				<div>
-					<div class="flex items-center justify-between gap-4 px-4 py-4">
-						<h1 class="text-immich-primary dark:text-immich-dark-primary font-medium">
-							🚨 Error - Something went wrong
-						</h1>
-						<div class="flex justify-end">
-							<button
-								on:click={() => handleCopy()}
-								class="transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-3 py-2 text-white rounded-full shadow-md text-sm"
-							>
-								<ContentCopy size={24} />
-							</button>
-						</div>
-					</div>
+  <div class="fixed top-0 w-full h-full bg-black/50 flex place-items-center place-content-center overflow-hidden">
+    <div>
+      <div
+        class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray shadow-sm w-[500px] max-w-[95vw] rounded-3xl dark:text-immich-dark-fg"
+      >
+        <div>
+          <div class="flex items-center justify-between gap-4 px-4 py-4">
+            <h1 class="text-immich-primary dark:text-immich-dark-primary font-medium">
+              🚨 Error - Something went wrong
+            </h1>
+            <div class="flex justify-end">
+              <button
+                on:click={() => handleCopy()}
+                class="transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-3 py-2 text-white rounded-full shadow-md text-sm"
+              >
+                <ContentCopy size={24} />
+              </button>
+            </div>
+          </div>
 
-					<hr />
+          <hr />
 
-					<div class="p-4 max-h-[75vh] min-h-[300px] overflow-y-auto immich-scrollbar pb-4 gap-4">
-						<div class="flex flex-col w-full gap-2">
-							<p class="text-red-500">{$page.error?.message} ({$page.error?.code})</p>
-							{#if $page.error?.stack}
-								<label for="stacktrace">Stacktrace</label>
-								<pre id="stacktrace" class="text-xs">{$page.error?.stack || 'No stack'}</pre>
-							{/if}
-						</div>
-					</div>
+          <div class="p-4 max-h-[75vh] min-h-[300px] overflow-y-auto immich-scrollbar pb-4 gap-4">
+            <div class="flex flex-col w-full gap-2">
+              <p class="text-red-500">{$page.error?.message} ({$page.error?.code})</p>
+              {#if $page.error?.stack}
+                <label for="stacktrace">Stacktrace</label>
+                <pre id="stacktrace" class="text-xs">{$page.error?.stack || 'No stack'}</pre>
+              {/if}
+            </div>
+          </div>
 
-					<hr />
+          <hr />
 
-					<div class="flex justify-around place-items-center place-content-center">
-						<!-- href="https://github.com/immich-app/immich/issues/new" -->
-						<a
-							href="https://discord.com/invite/D8JsnBEuKb"
-							target="_blank"
-							rel="noopener noreferrer"
-							class="flex justify-center grow basis-0 p-4"
-						>
-							<button class="flex flex-col gap-2 place-items-center place-content-center">
-								<Message size={24} />
-								<p class="text-sm">Get Help</p>
-							</button>
-						</a>
+          <div class="flex justify-around place-items-center place-content-center">
+            <!-- href="https://github.com/immich-app/immich/issues/new" -->
+            <a
+              href="https://discord.com/invite/D8JsnBEuKb"
+              target="_blank"
+              rel="noopener noreferrer"
+              class="flex justify-center grow basis-0 p-4"
+            >
+              <button class="flex flex-col gap-2 place-items-center place-content-center">
+                <Message size={24} />
+                <p class="text-sm">Get Help</p>
+              </button>
+            </a>
 
-						<a
-							href="https://github.com/immich-app/immich/releases"
-							target="_blank"
-							rel="noopener noreferrer"
-							class="flex justify-center grow basis-0 p-4"
-						>
-							<button class="flex flex-col gap-2 place-items-center place-content-center">
-								<PartyPopper size={24} />
-								<p class="text-sm">Read Changelog</p>
-							</button>
-						</a>
+            <a
+              href="https://github.com/immich-app/immich/releases"
+              target="_blank"
+              rel="noopener noreferrer"
+              class="flex justify-center grow basis-0 p-4"
+            >
+              <button class="flex flex-col gap-2 place-items-center place-content-center">
+                <PartyPopper size={24} />
+                <p class="text-sm">Read Changelog</p>
+              </button>
+            </a>
 
-						<a
-							href="https://immich.app/docs/guides/docker-help"
-							target="_blank"
-							rel="noopener noreferrer"
-							class="flex justify-center grow basis-0 p-4"
-						>
-							<button class="flex flex-col gap-2 place-items-center place-content-center">
-								<CodeTags size={24} />
-								<p class="text-sm">Check Logs</p>
-							</button>
-						</a>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
+            <a
+              href="https://immich.app/docs/guides/docker-help"
+              target="_blank"
+              rel="noopener noreferrer"
+              class="flex justify-center grow basis-0 p-4"
+            >
+              <button class="flex flex-col gap-2 place-items-center place-content-center">
+                <CodeTags size={24} />
+                <p class="text-sm">Check Logs</p>
+              </button>
+            </a>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 </div>
diff --git a/web/src/routes/+layout.server.ts b/web/src/routes/+layout.server.ts
index bf504b7efa..6ee3890737 100644
--- a/web/src/routes/+layout.server.ts
+++ b/web/src/routes/+layout.server.ts
@@ -1,7 +1,7 @@
 import type { LayoutServerLoad } from './$types';
 
 export const load = (async ({ locals: { api, user } }) => {
-	const { data: serverVersion } = await api.serverInfoApi.getServerVersion();
+  const { data: serverVersion } = await api.serverInfoApi.getServerVersion();
 
-	return { serverVersion, user };
+  return { serverVersion, user };
 }) satisfies LayoutServerLoad;
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index f817768bfd..a03592aca3 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -1,86 +1,86 @@
 <script lang="ts">
-	import '../app.css';
+  import '../app.css';
 
-	import { page } from '$app/stores';
-	import { afterNavigate, beforeNavigate } from '$app/navigation';
-	import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
-	import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
-	import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
-	import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
-	import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte';
-	import type { LayoutData } from './$types';
-	import { fileUploadHandler } from '$lib/utils/file-uploader';
-	import UploadCover from '$lib/components/shared-components/drag-and-drop-upload-overlay.svelte';
-	import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
-	import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
-	import FaviconHeader from '$lib/components/shared-components/favicon-header.svelte';
+  import { page } from '$app/stores';
+  import { afterNavigate, beforeNavigate } from '$app/navigation';
+  import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
+  import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
+  import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
+  import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
+  import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte';
+  import type { LayoutData } from './$types';
+  import { fileUploadHandler } from '$lib/utils/file-uploader';
+  import UploadCover from '$lib/components/shared-components/drag-and-drop-upload-overlay.svelte';
+  import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
+  import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
+  import FaviconHeader from '$lib/components/shared-components/favicon-header.svelte';
 
-	import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
+  import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
 
-	let showNavigationLoadingBar = false;
-	export let data: LayoutData;
-	let albumId: string | undefined;
+  let showNavigationLoadingBar = false;
+  export let data: LayoutData;
+  let albumId: string | undefined;
 
-	beforeNavigate(() => {
-		showNavigationLoadingBar = true;
-	});
+  beforeNavigate(() => {
+    showNavigationLoadingBar = true;
+  });
 
-	afterNavigate(() => {
-		showNavigationLoadingBar = false;
-	});
+  afterNavigate(() => {
+    showNavigationLoadingBar = false;
+  });
 
-	const dropHandler = async ({ dataTransfer }: DragEvent) => {
-		const files = dataTransfer?.files;
-		if (!files) {
-			return;
-		}
+  const dropHandler = async ({ dataTransfer }: DragEvent) => {
+    const files = dataTransfer?.files;
+    if (!files) {
+      return;
+    }
 
-		const filesArray: File[] = Array.from<File>(files);
-		albumId = ($page.route.id === '/(user)/albums/[albumId]' || undefined) && $page.params.albumId;
+    const filesArray: File[] = Array.from<File>(files);
+    albumId = ($page.route.id === '/(user)/albums/[albumId]' || undefined) && $page.params.albumId;
 
-		const isShare = $page.route.id === '/(user)/share/[key]' || undefined;
-		if (isShare) {
-			dragAndDropFilesStore.set({ isDragging: true, files: filesArray });
-		} else {
-			await fileUploadHandler(filesArray, albumId);
-		}
-	};
+    const isShare = $page.route.id === '/(user)/share/[key]' || undefined;
+    if (isShare) {
+      dragAndDropFilesStore.set({ isDragging: true, files: filesArray });
+    } else {
+      await fileUploadHandler(filesArray, albumId);
+    }
+  };
 </script>
 
 <svelte:head>
-	<title>{$page.data.meta?.title || 'Web'} - Immich</title>
-	<link rel="manifest" href="/manifest.json" />
-	<meta name="theme-color" content="currentColor" />
-	<FaviconHeader />
-	<AppleHeader />
+  <title>{$page.data.meta?.title || 'Web'} - Immich</title>
+  <link rel="manifest" href="/manifest.json" />
+  <meta name="theme-color" content="currentColor" />
+  <FaviconHeader />
+  <AppleHeader />
 
-	{#if $page.data.meta}
-		<meta name="description" content={$page.data.meta.description} />
+  {#if $page.data.meta}
+    <meta name="description" content={$page.data.meta.description} />
 
-		<!-- Facebook Meta Tags -->
-		<meta property="og:type" content="website" />
-		<meta property="og:title" content={$page.data.meta.title} />
-		<meta property="og:description" content={$page.data.meta.description} />
-		<meta property="og:image" content={$page.data.meta.imageUrl} />
+    <!-- Facebook Meta Tags -->
+    <meta property="og:type" content="website" />
+    <meta property="og:title" content={$page.data.meta.title} />
+    <meta property="og:description" content={$page.data.meta.description} />
+    <meta property="og:image" content={$page.data.meta.imageUrl} />
 
-		<!-- Twitter Meta Tags -->
-		<meta name="twitter:card" content="summary_large_image" />
-		<meta name="twitter:title" content={$page.data.meta.title} />
-		<meta name="twitter:description" content={$page.data.meta.description} />
-		<meta name="twitter:image" content={$page.data.meta.imageUrl} />
-	{/if}
+    <!-- Twitter Meta Tags -->
+    <meta name="twitter:card" content="summary_large_image" />
+    <meta name="twitter:title" content={$page.data.meta.title} />
+    <meta name="twitter:description" content={$page.data.meta.description} />
+    <meta name="twitter:image" content={$page.data.meta.imageUrl} />
+  {/if}
 </svelte:head>
 
 <noscript
-	class="h-screen w-screen absolute z-[1000] flex place-items-center place-content-center bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg"
+  class="h-screen w-screen absolute z-[1000] flex place-items-center place-content-center bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg"
 >
-	<FullscreenContainer title="Welcome to Immich">
-		To use Immich, you must enable JavaScript or use a JavaScript compatible browser.
-	</FullscreenContainer>
+  <FullscreenContainer title="Welcome to Immich">
+    To use Immich, you must enable JavaScript or use a JavaScript compatible browser.
+  </FullscreenContainer>
 </noscript>
 
 {#if showNavigationLoadingBar}
-	<NavigationLoadingBar />
+  <NavigationLoadingBar />
 {/if}
 
 <slot {albumId} />
@@ -90,9 +90,9 @@
 <NotificationList />
 
 {#if data.user?.isAdmin}
-	<VersionAnnouncementBox serverVersion={data.serverVersion} />
+  <VersionAnnouncementBox serverVersion={data.serverVersion} />
 {/if}
 
 {#if $page.route.id?.includes('(user)')}
-	<UploadCover {dropHandler} />
+  <UploadCover {dropHandler} />
 {/if}
diff --git a/web/src/routes/+page.server.ts b/web/src/routes/+page.server.ts
index c2227e2fcf..51a6ef71d0 100644
--- a/web/src/routes/+page.server.ts
+++ b/web/src/routes/+page.server.ts
@@ -1,26 +1,26 @@
 export const prerender = false;
 
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ parent, locals: { api } }) => {
-	const { user } = await parent();
-	if (user) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  const { user } = await parent();
+  if (user) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	const { data } = await api.userApi.getUserCount({ admin: true });
+  const { data } = await api.userApi.getUserCount({ admin: true });
 
-	if (data.userCount > 0) {
-		// Redirect to login page if an admin is already registered.
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  if (data.userCount > 0) {
+    // Redirect to login page if an admin is already registered.
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		meta: {
-			title: 'Welcome 🎉',
-			description: 'Immich Web Interface'
-		}
-	};
+  return {
+    meta: {
+      title: 'Welcome 🎉',
+      description: 'Immich Web Interface',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
index 67e30e734b..4006da311c 100644
--- a/web/src/routes/+page.svelte
+++ b/web/src/routes/+page.svelte
@@ -1,21 +1,19 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import Button from '$lib/components/elements/buttons/button.svelte';
-	import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
+  import { goto } from '$app/navigation';
+  import Button from '$lib/components/elements/buttons/button.svelte';
+  import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
 </script>
 
 <section class="h-screen w-screen flex place-items-center place-content-center">
-	<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
-		<div class="flex place-items-center place-content-center">
-			<ImmichLogo class="text-center" height="200" width="200" />
-		</div>
-		<h1
-			class="text-4xl text-immich-primary dark:text-immich-dark-primary font-bold font-immich-title"
-		>
-			Welcome to IMMICH Web
-		</h1>
-		<Button size="lg" rounded="lg" on:click={() => goto('/auth/register')}>
-			<span class="font-bold px-2">Getting Started</span>
-		</Button>
-	</div>
+  <div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
+    <div class="flex place-items-center place-content-center">
+      <ImmichLogo class="text-center" height="200" width="200" />
+    </div>
+    <h1 class="text-4xl text-immich-primary dark:text-immich-dark-primary font-bold font-immich-title">
+      Welcome to IMMICH Web
+    </h1>
+    <Button size="lg" rounded="lg" on:click={() => goto('/auth/register')}>
+      <span class="font-bold px-2">Getting Started</span>
+    </Button>
+  </div>
 </section>
diff --git a/web/src/routes/.well-known/immich/+server.ts b/web/src/routes/.well-known/immich/+server.ts
index 8ae494123b..37d7788062 100644
--- a/web/src/routes/.well-known/immich/+server.ts
+++ b/web/src/routes/.well-known/immich/+server.ts
@@ -3,9 +3,9 @@ import { json } from '@sveltejs/kit';
 const endpoint = process.env.IMMICH_API_URL_EXTERNAL || '/api';
 
 export const GET = async () => {
-	return json({
-		api: {
-			endpoint
-		}
-	});
+  return json({
+    api: {
+      endpoint,
+    },
+  });
 };
diff --git a/web/src/routes/admin/+layout.svelte b/web/src/routes/admin/+layout.svelte
index 98a16ab1dc..61800f6beb 100644
--- a/web/src/routes/admin/+layout.svelte
+++ b/web/src/routes/admin/+layout.svelte
@@ -1,77 +1,77 @@
 <script lang="ts">
-	// DO NOT include `import { page } from '$app/stores'` here, because this can
-	// lead to pages not being unmounted, which then causes weird page nesting
-	// and routing issues.
-	//
-	// This is an issue in SvelteKit caused by using the page store in layouts and
-	// using transitions on pages: https://github.com/sveltejs/kit/issues/7405
+  // DO NOT include `import { page } from '$app/stores'` here, because this can
+  // lead to pages not being unmounted, which then causes weird page nesting
+  // and routing issues.
+  //
+  // This is an issue in SvelteKit caused by using the page store in layouts and
+  // using transitions on pages: https://github.com/sveltejs/kit/issues/7405
 
-	import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
-	import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
-	import Sync from 'svelte-material-icons/Sync.svelte';
-	import Cog from 'svelte-material-icons/Cog.svelte';
-	import Server from 'svelte-material-icons/Server.svelte';
-	import StatusBox from '$lib/components/shared-components/status-box.svelte';
-	import { goto } from '$app/navigation';
-	import { AppRoute } from '../../lib/constants';
-	import type { LayoutData } from './$types';
-	import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+  import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
+  import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
+  import Sync from 'svelte-material-icons/Sync.svelte';
+  import Cog from 'svelte-material-icons/Cog.svelte';
+  import Server from 'svelte-material-icons/Server.svelte';
+  import StatusBox from '$lib/components/shared-components/status-box.svelte';
+  import { goto } from '$app/navigation';
+  import { AppRoute } from '../../lib/constants';
+  import type { LayoutData } from './$types';
+  import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
+  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 
-	export let data: LayoutData;
+  export let data: LayoutData;
 
-	// Circumvents the need to import the page store. Should be replaced by
-	// `$page.data.meta.title` once issue #7405 of SvelteKit is resolved.
-	const getPageTitle = (routeId: string | null) => {
-		switch (routeId) {
-			case AppRoute.ADMIN_USER_MANAGEMENT:
-				return 'User Management';
-			case AppRoute.ADMIN_SETTINGS:
-				return 'System Settings';
-			case AppRoute.ADMIN_JOBS:
-				return 'Jobs';
-			case AppRoute.ADMIN_STATS:
-				return 'Server Stats';
-			default:
-				return '';
-		}
-	};
+  // Circumvents the need to import the page store. Should be replaced by
+  // `$page.data.meta.title` once issue #7405 of SvelteKit is resolved.
+  const getPageTitle = (routeId: string | null) => {
+    switch (routeId) {
+      case AppRoute.ADMIN_USER_MANAGEMENT:
+        return 'User Management';
+      case AppRoute.ADMIN_SETTINGS:
+        return 'System Settings';
+      case AppRoute.ADMIN_JOBS:
+        return 'Jobs';
+      case AppRoute.ADMIN_STATS:
+        return 'Server Stats';
+      default:
+        return '';
+    }
+  };
 </script>
 
 <UserPageLayout user={data.user} showUploadButton={false} title={getPageTitle(data.routeId)}>
-	<SideBarSection slot="sidebar">
-		<SideBarButton
-			title="Users"
-			logo={AccountMultipleOutline}
-			isSelected={data.routeId === AppRoute.ADMIN_USER_MANAGEMENT}
-			on:selected={() => goto(AppRoute.ADMIN_USER_MANAGEMENT)}
-		/>
-		<SideBarButton
-			title="Jobs"
-			logo={Sync}
-			isSelected={data.routeId === AppRoute.ADMIN_JOBS}
-			on:selected={() => goto(AppRoute.ADMIN_JOBS)}
-		/>
-		<SideBarButton
-			title="Settings"
-			logo={Cog}
-			isSelected={data.routeId === AppRoute.ADMIN_SETTINGS}
-			on:selected={() => goto(AppRoute.ADMIN_SETTINGS)}
-		/>
-		<SideBarButton
-			title="Server Stats"
-			logo={Server}
-			isSelected={data.routeId === AppRoute.ADMIN_STATS}
-			on:selected={() => goto(AppRoute.ADMIN_STATS)}
-		/>
-		<div class="mb-6 mt-auto">
-			<StatusBox />
-		</div>
-	</SideBarSection>
+  <SideBarSection slot="sidebar">
+    <SideBarButton
+      title="Users"
+      logo={AccountMultipleOutline}
+      isSelected={data.routeId === AppRoute.ADMIN_USER_MANAGEMENT}
+      on:selected={() => goto(AppRoute.ADMIN_USER_MANAGEMENT)}
+    />
+    <SideBarButton
+      title="Jobs"
+      logo={Sync}
+      isSelected={data.routeId === AppRoute.ADMIN_JOBS}
+      on:selected={() => goto(AppRoute.ADMIN_JOBS)}
+    />
+    <SideBarButton
+      title="Settings"
+      logo={Cog}
+      isSelected={data.routeId === AppRoute.ADMIN_SETTINGS}
+      on:selected={() => goto(AppRoute.ADMIN_SETTINGS)}
+    />
+    <SideBarButton
+      title="Server Stats"
+      logo={Server}
+      isSelected={data.routeId === AppRoute.ADMIN_STATS}
+      on:selected={() => goto(AppRoute.ADMIN_STATS)}
+    />
+    <div class="mb-6 mt-auto">
+      <StatusBox />
+    </div>
+  </SideBarSection>
 
-	<section id="setting-content" class="flex place-content-center mx-4">
-		<section class="w-full sm:w-5/6 md:w-[800px] pt-5 pb-28">
-			<slot />
-		</section>
-	</section>
+  <section id="setting-content" class="flex place-content-center mx-4">
+    <section class="w-full sm:w-5/6 md:w-[800px] pt-5 pb-28">
+      <slot />
+    </section>
+  </section>
 </UserPageLayout>
diff --git a/web/src/routes/admin/+layout.ts b/web/src/routes/admin/+layout.ts
index e2128861b9..edabd3d067 100644
--- a/web/src/routes/admin/+layout.ts
+++ b/web/src/routes/admin/+layout.ts
@@ -3,12 +3,12 @@ import { redirect } from '@sveltejs/kit';
 import type { LayoutLoad } from './$types';
 
 export const load = (async ({ parent, route }) => {
-	const { user } = await parent();
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else if (!user.isAdmin) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  const { user } = await parent();
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else if (!user.isAdmin) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	return { routeId: route.id, user };
+  return { routeId: route.id, user };
 }) satisfies LayoutLoad;
diff --git a/web/src/routes/admin/+page.server.ts b/web/src/routes/admin/+page.server.ts
index bb148ff37b..777b940dcd 100644
--- a/web/src/routes/admin/+page.server.ts
+++ b/web/src/routes/admin/+page.server.ts
@@ -1,15 +1,15 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load: PageServerLoad = async ({ parent }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else if (!user.isAdmin) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else if (!user.isAdmin) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	throw redirect(302, AppRoute.ADMIN_USER_MANAGEMENT);
+  throw redirect(302, AppRoute.ADMIN_USER_MANAGEMENT);
 };
diff --git a/web/src/routes/admin/jobs-status/+page.server.ts b/web/src/routes/admin/jobs-status/+page.server.ts
index f20e0010e2..7407fee654 100644
--- a/web/src/routes/admin/jobs-status/+page.server.ts
+++ b/web/src/routes/admin/jobs-status/+page.server.ts
@@ -3,23 +3,23 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user, api } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else if (!user.isAdmin) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else if (!user.isAdmin) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	try {
-		const { data: jobs } = await api.jobApi.getAllJobsStatus();
+  try {
+    const { data: jobs } = await api.jobApi.getAllJobsStatus();
 
-		return {
-			jobs,
-			meta: {
-				title: 'Job Status'
-			}
-		};
-	} catch (err) {
-		console.error('[jobs] > getAllJobsStatus', err);
-		throw err;
-	}
+    return {
+      jobs,
+      meta: {
+        title: 'Job Status',
+      },
+    };
+  } catch (err) {
+    console.error('[jobs] > getAllJobsStatus', err);
+    throw err;
+  }
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte
index e69cb68987..a30a9fffda 100644
--- a/web/src/routes/admin/jobs-status/+page.svelte
+++ b/web/src/routes/admin/jobs-status/+page.svelte
@@ -1,27 +1,27 @@
 <script lang="ts">
-	import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte';
-	import { api } from '@api';
-	import { onDestroy, onMount } from 'svelte';
-	import type { PageData } from './$types';
+  import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte';
+  import { api } from '@api';
+  import { onDestroy, onMount } from 'svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
-	let timer: NodeJS.Timer;
+  export let data: PageData;
+  let timer: NodeJS.Timer;
 
-	$: jobs = data.jobs;
+  $: jobs = data.jobs;
 
-	const load = async () => {
-		const { data } = await api.jobApi.getAllJobsStatus();
-		jobs = data;
-	};
+  const load = async () => {
+    const { data } = await api.jobApi.getAllJobsStatus();
+    jobs = data;
+  };
 
-	onMount(async () => {
-		await load();
-		timer = setInterval(load, 5_000);
-	});
+  onMount(async () => {
+    await load();
+    timer = setInterval(load, 5_000);
+  });
 
-	onDestroy(() => {
-		clearInterval(timer);
-	});
+  onDestroy(() => {
+    clearInterval(timer);
+  });
 </script>
 
 <JobsPanel {jobs} />
diff --git a/web/src/routes/admin/server-status/+page.server.ts b/web/src/routes/admin/server-status/+page.server.ts
index 2e366f365a..477e27d8be 100644
--- a/web/src/routes/admin/server-status/+page.server.ts
+++ b/web/src/routes/admin/server-status/+page.server.ts
@@ -1,22 +1,22 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ parent, locals: { api } }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else if (!user.isAdmin) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else if (!user.isAdmin) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	const { data: stats } = await api.serverInfoApi.getStats();
+  const { data: stats } = await api.serverInfoApi.getStats();
 
-	return {
-		stats,
-		meta: {
-			title: 'Server Stats'
-		}
-	};
+  return {
+    stats,
+    meta: {
+      title: 'Server Stats',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/admin/server-status/+page.svelte b/web/src/routes/admin/server-status/+page.svelte
index 8a930af796..e096ff002e 100644
--- a/web/src/routes/admin/server-status/+page.svelte
+++ b/web/src/routes/admin/server-status/+page.svelte
@@ -1,22 +1,22 @@
 <script lang="ts">
-	import { onMount, onDestroy } from 'svelte';
-	import { api } from '@api';
-	import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte';
-	import type { PageData } from './$types';
+  import { onMount, onDestroy } from 'svelte';
+  import { api } from '@api';
+  import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
-	let setIntervalHandler: NodeJS.Timer;
+  export let data: PageData;
+  let setIntervalHandler: NodeJS.Timer;
 
-	onMount(async () => {
-		setIntervalHandler = setInterval(async () => {
-			const { data: stats } = await api.serverInfoApi.getStats();
-			data.stats = stats;
-		}, 5000);
-	});
+  onMount(async () => {
+    setIntervalHandler = setInterval(async () => {
+      const { data: stats } = await api.serverInfoApi.getStats();
+      data.stats = stats;
+    }, 5000);
+  });
 
-	onDestroy(() => {
-		clearInterval(setIntervalHandler);
-	});
+  onDestroy(() => {
+    clearInterval(setIntervalHandler);
+  });
 </script>
 
 <ServerStatsPanel stats={data.stats} />
diff --git a/web/src/routes/admin/system-settings/+page.server.ts b/web/src/routes/admin/system-settings/+page.server.ts
index 2550325c99..65a9485de9 100644
--- a/web/src/routes/admin/system-settings/+page.server.ts
+++ b/web/src/routes/admin/system-settings/+page.server.ts
@@ -1,20 +1,20 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load: PageServerLoad = async ({ parent }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else if (!user.isAdmin) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else if (!user.isAdmin) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'System Settings'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'System Settings',
+    },
+  };
 };
diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte
index e6274900df..24a1532fdb 100644
--- a/web/src/routes/admin/system-settings/+page.svelte
+++ b/web/src/routes/admin/system-settings/+page.svelte
@@ -1,59 +1,56 @@
 <script lang="ts">
-	import { page } from '$app/stores';
-	import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte';
-	import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
-	import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
-	import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
-	import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
-	import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte';
-	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
-	import { api } from '@api';
-	import type { PageData } from './$types';
+  import { page } from '$app/stores';
+  import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte';
+  import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
+  import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
+  import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
+  import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
+  import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte';
+  import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
+  import { api } from '@api';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	const getConfig = async () => {
-		const { data } = await api.systemConfigApi.getConfig();
-		return data;
-	};
+  const getConfig = async () => {
+    const { data } = await api.systemConfigApi.getConfig();
+    return data;
+  };
 </script>
 
 <section class="">
-	{#await getConfig()}
-		<LoadingSpinner />
-	{:then configs}
-		<SettingAccordion
-			title="FFmpeg Settings"
-			subtitle="Manage the resolution and encoding information of the video files"
-		>
-			<FFmpegSettings ffmpegConfig={configs.ffmpeg} />
-		</SettingAccordion>
+  {#await getConfig()}
+    <LoadingSpinner />
+  {:then configs}
+    <SettingAccordion
+      title="FFmpeg Settings"
+      subtitle="Manage the resolution and encoding information of the video files"
+    >
+      <FFmpegSettings ffmpegConfig={configs.ffmpeg} />
+    </SettingAccordion>
 
-		<SettingAccordion
-			title="Job Settings"
-			subtitle="Manage job concurrency"
-			isOpen={$page.url.searchParams.get('open') === 'job-settings'}
-		>
-			<JobSettings jobConfig={configs.job} />
-		</SettingAccordion>
+    <SettingAccordion
+      title="Job Settings"
+      subtitle="Manage job concurrency"
+      isOpen={$page.url.searchParams.get('open') === 'job-settings'}
+    >
+      <JobSettings jobConfig={configs.job} />
+    </SettingAccordion>
 
-		<SettingAccordion
-			title="Password Authentication"
-			subtitle="Manage login with password settings"
-		>
-			<PasswordLoginSettings passwordLoginConfig={configs.passwordLogin} />
-		</SettingAccordion>
+    <SettingAccordion title="Password Authentication" subtitle="Manage login with password settings">
+      <PasswordLoginSettings passwordLoginConfig={configs.passwordLogin} />
+    </SettingAccordion>
 
-		<SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
-			<OAuthSettings oauthConfig={configs.oauth} />
-		</SettingAccordion>
+    <SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
+      <OAuthSettings oauthConfig={configs.oauth} />
+    </SettingAccordion>
 
-		<SettingAccordion
-			title="Storage Template"
-			subtitle="Manage the folder structure and file name of the upload asset"
-			isOpen={$page.url.searchParams.get('open') === 'storage-template'}
-		>
-			<StorageTemplateSettings storageConfig={configs.storageTemplate} user={data.user} />
-		</SettingAccordion>
-	{/await}
+    <SettingAccordion
+      title="Storage Template"
+      subtitle="Manage the folder structure and file name of the upload asset"
+      isOpen={$page.url.searchParams.get('open') === 'storage-template'}
+    >
+      <StorageTemplateSettings storageConfig={configs.storageTemplate} user={data.user} />
+    </SettingAccordion>
+  {/await}
 </section>
diff --git a/web/src/routes/admin/user-management/+page.server.ts b/web/src/routes/admin/user-management/+page.server.ts
index 661ef6bccb..54fdc4311c 100644
--- a/web/src/routes/admin/user-management/+page.server.ts
+++ b/web/src/routes/admin/user-management/+page.server.ts
@@ -1,23 +1,23 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ parent, locals: { api } }) => {
-	const { user } = await parent();
+  const { user } = await parent();
 
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else if (!user.isAdmin) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else if (!user.isAdmin) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	const { data: allUsers } = await api.userApi.getAllUsers({ isAll: false });
+  const { data: allUsers } = await api.userApi.getAllUsers({ isAll: false });
 
-	return {
-		user,
-		allUsers,
-		meta: {
-			title: 'User Management'
-		}
-	};
+  return {
+    user,
+    allUsers,
+    meta: {
+      title: 'User Management',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte
index aae793d474..aa9f964d6a 100644
--- a/web/src/routes/admin/user-management/+page.svelte
+++ b/web/src/routes/admin/user-management/+page.svelte
@@ -1,299 +1,295 @@
 <script lang="ts">
-	import { api, UserResponseDto } from '@api';
+  import { api, UserResponseDto } from '@api';
 
-	import { onMount } from 'svelte';
-	import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
-	import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
-	import DeleteRestore from 'svelte-material-icons/DeleteRestore.svelte';
-	import Check from 'svelte-material-icons/Check.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
-	import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
-	import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
-	import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialoge.svelte';
-	import RestoreDialogue from '$lib/components/admin-page/restore-dialoge.svelte';
-	import { page } from '$app/stores';
-	import { locale } from '$lib/stores/preferences.store';
-	import Button from '$lib/components/elements/buttons/button.svelte';
-	import type { PageData } from './$types';
+  import { onMount } from 'svelte';
+  import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
+  import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
+  import DeleteRestore from 'svelte-material-icons/DeleteRestore.svelte';
+  import Check from 'svelte-material-icons/Check.svelte';
+  import Close from 'svelte-material-icons/Close.svelte';
+  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
+  import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
+  import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
+  import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialoge.svelte';
+  import RestoreDialogue from '$lib/components/admin-page/restore-dialoge.svelte';
+  import { page } from '$app/stores';
+  import { locale } from '$lib/stores/preferences.store';
+  import Button from '$lib/components/elements/buttons/button.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	let allUsers: UserResponseDto[] = [];
-	let shouldShowEditUserForm = false;
-	let shouldShowCreateUserForm = false;
-	let shouldShowInfoPanel = false;
-	let shouldShowDeleteConfirmDialog = false;
-	let shouldShowRestoreDialog = false;
-	let selectedUser: UserResponseDto;
+  let allUsers: UserResponseDto[] = [];
+  let shouldShowEditUserForm = false;
+  let shouldShowCreateUserForm = false;
+  let shouldShowInfoPanel = false;
+  let shouldShowDeleteConfirmDialog = false;
+  let shouldShowRestoreDialog = false;
+  let selectedUser: UserResponseDto;
 
-	onMount(() => {
-		allUsers = $page.data.allUsers;
-	});
+  onMount(() => {
+    allUsers = $page.data.allUsers;
+  });
 
-	const isDeleted = (user: UserResponseDto): boolean => {
-		return user.deletedAt != null;
-	};
+  const isDeleted = (user: UserResponseDto): boolean => {
+    return user.deletedAt != null;
+  };
 
-	const deleteDateFormat: Intl.DateTimeFormatOptions = {
-		month: 'long',
-		day: 'numeric',
-		year: 'numeric'
-	};
+  const deleteDateFormat: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    day: 'numeric',
+    year: 'numeric',
+  };
 
-	const getDeleteDate = (user: UserResponseDto): string => {
-		let deletedAt = new Date(user.deletedAt ? user.deletedAt : Date.now());
-		deletedAt.setDate(deletedAt.getDate() + 7);
-		return deletedAt.toLocaleString($locale, deleteDateFormat);
-	};
+  const getDeleteDate = (user: UserResponseDto): string => {
+    let deletedAt = new Date(user.deletedAt ? user.deletedAt : Date.now());
+    deletedAt.setDate(deletedAt.getDate() + 7);
+    return deletedAt.toLocaleString($locale, deleteDateFormat);
+  };
 
-	const onUserCreated = async () => {
-		const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
-		allUsers = getAllUsersRes.data;
-		shouldShowCreateUserForm = false;
-	};
+  const onUserCreated = async () => {
+    const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
+    allUsers = getAllUsersRes.data;
+    shouldShowCreateUserForm = false;
+  };
 
-	const editUserHandler = async (user: UserResponseDto) => {
-		selectedUser = user;
-		shouldShowEditUserForm = true;
-	};
+  const editUserHandler = async (user: UserResponseDto) => {
+    selectedUser = user;
+    shouldShowEditUserForm = true;
+  };
 
-	const onEditUserSuccess = async () => {
-		const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
-		allUsers = getAllUsersRes.data;
-		shouldShowEditUserForm = false;
-	};
+  const onEditUserSuccess = async () => {
+    const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
+    allUsers = getAllUsersRes.data;
+    shouldShowEditUserForm = false;
+  };
 
-	const onEditPasswordSuccess = async () => {
-		const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
-		allUsers = getAllUsersRes.data;
-		shouldShowEditUserForm = false;
-		shouldShowInfoPanel = true;
-	};
+  const onEditPasswordSuccess = async () => {
+    const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
+    allUsers = getAllUsersRes.data;
+    shouldShowEditUserForm = false;
+    shouldShowInfoPanel = true;
+  };
 
-	const deleteUserHandler = async (user: UserResponseDto) => {
-		selectedUser = user;
-		shouldShowDeleteConfirmDialog = true;
-	};
+  const deleteUserHandler = async (user: UserResponseDto) => {
+    selectedUser = user;
+    shouldShowDeleteConfirmDialog = true;
+  };
 
-	const onUserDeleteSuccess = async () => {
-		const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
-		allUsers = getAllUsersRes.data;
-		shouldShowDeleteConfirmDialog = false;
-	};
+  const onUserDeleteSuccess = async () => {
+    const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
+    allUsers = getAllUsersRes.data;
+    shouldShowDeleteConfirmDialog = false;
+  };
 
-	const onUserDeleteFail = async () => {
-		const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
-		allUsers = getAllUsersRes.data;
-		shouldShowDeleteConfirmDialog = false;
-	};
+  const onUserDeleteFail = async () => {
+    const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
+    allUsers = getAllUsersRes.data;
+    shouldShowDeleteConfirmDialog = false;
+  };
 
-	const restoreUserHandler = async (user: UserResponseDto) => {
-		selectedUser = user;
-		shouldShowRestoreDialog = true;
-	};
+  const restoreUserHandler = async (user: UserResponseDto) => {
+    selectedUser = user;
+    shouldShowRestoreDialog = true;
+  };
 
-	const onUserRestoreSuccess = async () => {
-		const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
-		allUsers = getAllUsersRes.data;
-		shouldShowRestoreDialog = false;
-	};
+  const onUserRestoreSuccess = async () => {
+    const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
+    allUsers = getAllUsersRes.data;
+    shouldShowRestoreDialog = false;
+  };
 
-	const onUserRestoreFail = async () => {
-		// show fail dialog
-		const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
-		allUsers = getAllUsersRes.data;
-		shouldShowRestoreDialog = false;
-	};
+  const onUserRestoreFail = async () => {
+    // show fail dialog
+    const getAllUsersRes = await api.userApi.getAllUsers({ isAll: false });
+    allUsers = getAllUsersRes.data;
+    shouldShowRestoreDialog = false;
+  };
 </script>
 
 <section>
-	{#if shouldShowCreateUserForm}
-		<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
-			<CreateUserForm on:user-created={onUserCreated} />
-		</FullScreenModal>
-	{/if}
+  {#if shouldShowCreateUserForm}
+    <FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
+      <CreateUserForm on:user-created={onUserCreated} />
+    </FullScreenModal>
+  {/if}
 
-	{#if shouldShowEditUserForm}
-		<FullScreenModal on:clickOutside={() => (shouldShowEditUserForm = false)}>
-			<EditUserForm
-				user={selectedUser}
-				canResetPassword={selectedUser?.id !== data.user.id}
-				on:edit-success={onEditUserSuccess}
-				on:reset-password-success={onEditPasswordSuccess}
-			/>
-		</FullScreenModal>
-	{/if}
+  {#if shouldShowEditUserForm}
+    <FullScreenModal on:clickOutside={() => (shouldShowEditUserForm = false)}>
+      <EditUserForm
+        user={selectedUser}
+        canResetPassword={selectedUser?.id !== data.user.id}
+        on:edit-success={onEditUserSuccess}
+        on:reset-password-success={onEditPasswordSuccess}
+      />
+    </FullScreenModal>
+  {/if}
 
-	{#if shouldShowDeleteConfirmDialog}
-		<DeleteConfirmDialog
-			user={selectedUser}
-			on:user-delete-success={onUserDeleteSuccess}
-			on:user-delete-fail={onUserDeleteFail}
-			on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
-		/>
-	{/if}
+  {#if shouldShowDeleteConfirmDialog}
+    <DeleteConfirmDialog
+      user={selectedUser}
+      on:user-delete-success={onUserDeleteSuccess}
+      on:user-delete-fail={onUserDeleteFail}
+      on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
+    />
+  {/if}
 
-	{#if shouldShowRestoreDialog}
-		<RestoreDialogue
-			user={selectedUser}
-			on:user-restore-success={onUserRestoreSuccess}
-			on:user-restore-fail={onUserRestoreFail}
-			on:cancel={() => (shouldShowRestoreDialog = false)}
-		/>
-	{/if}
+  {#if shouldShowRestoreDialog}
+    <RestoreDialogue
+      user={selectedUser}
+      on:user-restore-success={onUserRestoreSuccess}
+      on:user-restore-fail={onUserRestoreFail}
+      on:cancel={() => (shouldShowRestoreDialog = false)}
+    />
+  {/if}
 
-	{#if shouldShowInfoPanel}
-		<FullScreenModal on:clickOutside={() => (shouldShowInfoPanel = false)}>
-			<div class="border bg-white shadow-sm w-[500px] max-w-[95vw] rounded-3xl p-8 text-sm">
-				<h1 class="font-medium text-immich-primary text-lg mb-4">Password reset success</h1>
+  {#if shouldShowInfoPanel}
+    <FullScreenModal on:clickOutside={() => (shouldShowInfoPanel = false)}>
+      <div class="border bg-white shadow-sm w-[500px] max-w-[95vw] rounded-3xl p-8 text-sm">
+        <h1 class="font-medium text-immich-primary text-lg mb-4">Password reset success</h1>
 
-				<p>
-					The user's password has been reset to the default <code
-						class="font-bold bg-gray-200 px-2 py-1 rounded-md text-immich-primary">password</code
-					>
-					<br />
-					Please inform the user, and they will need to change the password at the next log-on.
-				</p>
+        <p>
+          The user's password has been reset to the default <code
+            class="font-bold bg-gray-200 px-2 py-1 rounded-md text-immich-primary">password</code
+          >
+          <br />
+          Please inform the user, and they will need to change the password at the next log-on.
+        </p>
 
-				<div class="flex w-full mt-6">
-					<Button fullwidth on:click={() => (shouldShowInfoPanel = false)}>Done</Button>
-				</div>
-			</div>
-		</FullScreenModal>
-	{/if}
+        <div class="flex w-full mt-6">
+          <Button fullwidth on:click={() => (shouldShowInfoPanel = false)}>Done</Button>
+        </div>
+      </div>
+    </FullScreenModal>
+  {/if}
 
-	<table class="text-left w-full my-5 sm:block hidden">
-		<thead
-			class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
-		>
-			<tr class="flex w-full place-items-center">
-				<th class="text-center w-1/4 font-medium text-sm">Email</th>
-				<th class="text-center w-1/4 font-medium text-sm">First name</th>
-				<th class="text-center w-1/4 font-medium text-sm">Last name</th>
-				<th class="text-center w-1/4 font-medium text-sm">Can import</th>
-				<th class="text-center w-1/4 font-medium text-sm">Action</th>
-			</tr>
-		</thead>
-		<tbody
-			class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray"
-		>
-			{#if allUsers}
-				{#each allUsers as user, i}
-					<tr
-						class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
-							isDeleted(user)
-								? 'bg-red-300 dark:bg-red-900'
-								: i % 2 == 0
-								? 'bg-immich-gray dark:bg-immich-dark-gray/75'
-								: 'bg-immich-bg dark:bg-immich-dark-gray/50'
-						}`}
-					>
-						<td class="text-sm px-4 w-1/4 text-ellipsis">{user.email}</td>
-						<td class="text-sm px-4 w-1/4 text-ellipsis">{user.firstName}</td>
-						<td class="text-sm px-4 w-1/4 text-ellipsis">{user.lastName}</td>
-						<td class="text-sm px-4 w-1/4 text-ellipsis">
-							<div class="container flex flex-wrap mx-auto justify-center">
-								{#if user.externalPath}
-									<Check size="16" />
-								{:else}
-									<Close size="16" />
-								{/if}
-							</div>
-						</td>
-						<td class="text-sm px-4 w-1/4 text-ellipsis">
-							{#if !isDeleted(user)}
-								<button
-									on:click={() => editUserHandler(user)}
-									class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
-								>
-									<PencilOutline size="16" />
-								</button>
-								{#if user.id !== data.user.id}
-									<button
-										on:click={() => deleteUserHandler(user)}
-										class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
-									>
-										<TrashCanOutline size="16" />
-									</button>
-								{/if}
-							{/if}
-							{#if isDeleted(user)}
-								<button
-									on:click={() => restoreUserHandler(user)}
-									class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
-									title={`scheduled removal on ${getDeleteDate(user)}`}
-								>
-									<DeleteRestore size="16" />
-								</button>
-							{/if}
-						</td>
-					</tr>
-				{/each}
-			{/if}
-		</tbody>
-	</table>
+  <table class="text-left w-full my-5 sm:block hidden">
+    <thead
+      class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
+    >
+      <tr class="flex w-full place-items-center">
+        <th class="text-center w-1/4 font-medium text-sm">Email</th>
+        <th class="text-center w-1/4 font-medium text-sm">First name</th>
+        <th class="text-center w-1/4 font-medium text-sm">Last name</th>
+        <th class="text-center w-1/4 font-medium text-sm">Can import</th>
+        <th class="text-center w-1/4 font-medium text-sm">Action</th>
+      </tr>
+    </thead>
+    <tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray">
+      {#if allUsers}
+        {#each allUsers as user, i}
+          <tr
+            class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
+              isDeleted(user)
+                ? 'bg-red-300 dark:bg-red-900'
+                : i % 2 == 0
+                ? 'bg-immich-gray dark:bg-immich-dark-gray/75'
+                : 'bg-immich-bg dark:bg-immich-dark-gray/50'
+            }`}
+          >
+            <td class="text-sm px-4 w-1/4 text-ellipsis">{user.email}</td>
+            <td class="text-sm px-4 w-1/4 text-ellipsis">{user.firstName}</td>
+            <td class="text-sm px-4 w-1/4 text-ellipsis">{user.lastName}</td>
+            <td class="text-sm px-4 w-1/4 text-ellipsis">
+              <div class="container flex flex-wrap mx-auto justify-center">
+                {#if user.externalPath}
+                  <Check size="16" />
+                {:else}
+                  <Close size="16" />
+                {/if}
+              </div>
+            </td>
+            <td class="text-sm px-4 w-1/4 text-ellipsis">
+              {#if !isDeleted(user)}
+                <button
+                  on:click={() => editUserHandler(user)}
+                  class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
+                >
+                  <PencilOutline size="16" />
+                </button>
+                {#if user.id !== data.user.id}
+                  <button
+                    on:click={() => deleteUserHandler(user)}
+                    class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
+                  >
+                    <TrashCanOutline size="16" />
+                  </button>
+                {/if}
+              {/if}
+              {#if isDeleted(user)}
+                <button
+                  on:click={() => restoreUserHandler(user)}
+                  class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
+                  title={`scheduled removal on ${getDeleteDate(user)}`}
+                >
+                  <DeleteRestore size="16" />
+                </button>
+              {/if}
+            </td>
+          </tr>
+        {/each}
+      {/if}
+    </tbody>
+  </table>
 
-	<table class="text-left w-full my-5 block sm:hidden">
-		<thead
-			class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
-		>
-			<tr class="flex w-full place-items-center">
-				<th class="text-center w-1/2 font-medium text-sm flex justify-around">
-					<span>Name</span>
-					<span>Email</span>
-				</th>
-				<th class="text-center w-1/2 font-medium text-sm">Action</th>
-			</tr>
-		</thead>
-		<tbody
-			class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray"
-		>
-			{#if allUsers}
-				{#each allUsers as user, i}
-					<tr
-						class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
-							isDeleted(user)
-								? 'bg-red-300 dark:bg-red-900'
-								: i % 2 == 0
-								? 'bg-immich-gray dark:bg-immich-dark-gray/75'
-								: 'bg-immich-bg dark:bg-immich-dark-gray/50'
-						}`}
-					>
-						<td class="text-sm px-4 w-2/3 text-ellipsis">
-							<span>{user.firstName} {user.lastName}</span>
-							<span>{user.email}</span>
-						</td>
-						<td class="text-sm px-4 w-1/3 text-ellipsis">
-							{#if !isDeleted(user)}
-								<button
-									on:click={() => editUserHandler(user)}
-									class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full sm:p-3 p-2 max-sm:mb-1 transition-all duration-150 hover:bg-immich-primary/75"
-								>
-									<PencilOutline size="16" />
-								</button>
-								<button
-									on:click={() => deleteUserHandler(user)}
-									class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full sm:p-3 p-2 transition-all duration-150 hover:bg-immich-primary/75"
-								>
-									<TrashCanOutline size="16" />
-								</button>
-							{/if}
-							{#if isDeleted(user)}
-								<button
-									on:click={() => restoreUserHandler(user)}
-									class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full sm:p-3 p-2 transition-all duration-150 hover:bg-immich-primary/75"
-									title={`scheduled removal on ${getDeleteDate(user)}`}
-								>
-									<DeleteRestore size="16" />
-								</button>
-							{/if}
-						</td>
-					</tr>
-				{/each}
-			{/if}
-		</tbody>
-	</table>
+  <table class="text-left w-full my-5 block sm:hidden">
+    <thead
+      class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
+    >
+      <tr class="flex w-full place-items-center">
+        <th class="text-center w-1/2 font-medium text-sm flex justify-around">
+          <span>Name</span>
+          <span>Email</span>
+        </th>
+        <th class="text-center w-1/2 font-medium text-sm">Action</th>
+      </tr>
+    </thead>
+    <tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray">
+      {#if allUsers}
+        {#each allUsers as user, i}
+          <tr
+            class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
+              isDeleted(user)
+                ? 'bg-red-300 dark:bg-red-900'
+                : i % 2 == 0
+                ? 'bg-immich-gray dark:bg-immich-dark-gray/75'
+                : 'bg-immich-bg dark:bg-immich-dark-gray/50'
+            }`}
+          >
+            <td class="text-sm px-4 w-2/3 text-ellipsis">
+              <span>{user.firstName} {user.lastName}</span>
+              <span>{user.email}</span>
+            </td>
+            <td class="text-sm px-4 w-1/3 text-ellipsis">
+              {#if !isDeleted(user)}
+                <button
+                  on:click={() => editUserHandler(user)}
+                  class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full sm:p-3 p-2 max-sm:mb-1 transition-all duration-150 hover:bg-immich-primary/75"
+                >
+                  <PencilOutline size="16" />
+                </button>
+                <button
+                  on:click={() => deleteUserHandler(user)}
+                  class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full sm:p-3 p-2 transition-all duration-150 hover:bg-immich-primary/75"
+                >
+                  <TrashCanOutline size="16" />
+                </button>
+              {/if}
+              {#if isDeleted(user)}
+                <button
+                  on:click={() => restoreUserHandler(user)}
+                  class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full sm:p-3 p-2 transition-all duration-150 hover:bg-immich-primary/75"
+                  title={`scheduled removal on ${getDeleteDate(user)}`}
+                >
+                  <DeleteRestore size="16" />
+                </button>
+              {/if}
+            </td>
+          </tr>
+        {/each}
+      {/if}
+    </tbody>
+  </table>
 
-	<Button size="sm" on:click={() => (shouldShowCreateUserForm = true)}>Create user</Button>
+  <Button size="sm" on:click={() => (shouldShowCreateUserForm = true)}>Create user</Button>
 </section>
diff --git a/web/src/routes/auth/change-password/+page.server.ts b/web/src/routes/auth/change-password/+page.server.ts
index b7ee95ded6..774f2f0b09 100644
--- a/web/src/routes/auth/change-password/+page.server.ts
+++ b/web/src/routes/auth/change-password/+page.server.ts
@@ -3,16 +3,16 @@ import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { user } }) => {
-	if (!user) {
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	} else if (!user.shouldChangePassword) {
-		throw redirect(302, AppRoute.PHOTOS);
-	}
+  if (!user) {
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  } else if (!user.shouldChangePassword) {
+    throw redirect(302, AppRoute.PHOTOS);
+  }
 
-	return {
-		user,
-		meta: {
-			title: 'Change Password'
-		}
-	};
+  return {
+    user,
+    meta: {
+      title: 'Change Password',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte
index 993f8c68fe..f515e1d442 100644
--- a/web/src/routes/auth/change-password/+page.svelte
+++ b/web/src/routes/auth/change-password/+page.svelte
@@ -1,28 +1,28 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
-	import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
-	import { AppRoute } from '$lib/constants';
-	import type { PageData } from './$types';
+  import { goto } from '$app/navigation';
+  import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
+  import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
+  import { AppRoute } from '$lib/constants';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 
-	const onSuccessHandler = async () => {
-		await fetch(AppRoute.AUTH_LOGOUT, { method: 'POST' });
+  const onSuccessHandler = async () => {
+    await fetch(AppRoute.AUTH_LOGOUT, { method: 'POST' });
 
-		goto(AppRoute.AUTH_LOGIN);
-	};
+    goto(AppRoute.AUTH_LOGIN);
+  };
 </script>
 
 <FullscreenContainer title={data.meta.title}>
-	<p slot="message">
-		Hi {data.user.firstName}
-		{data.user.lastName} ({data.user.email}),
-		<br />
-		<br />
-		This is either the first time you are signing into the system or a request has been made to change
-		your password. Please enter the new password below.
-	</p>
+  <p slot="message">
+    Hi {data.user.firstName}
+    {data.user.lastName} ({data.user.email}),
+    <br />
+    <br />
+    This is either the first time you are signing into the system or a request has been made to change your password. Please
+    enter the new password below.
+  </p>
 
-	<ChangePasswordForm user={data.user} on:success={onSuccessHandler} />
+  <ChangePasswordForm user={data.user} on:success={onSuccessHandler} />
 </FullscreenContainer>
diff --git a/web/src/routes/auth/login/+page.server.ts b/web/src/routes/auth/login/+page.server.ts
index 3d42639de3..5e10dcd55e 100644
--- a/web/src/routes/auth/login/+page.server.ts
+++ b/web/src/routes/auth/login/+page.server.ts
@@ -1,34 +1,34 @@
 import { AppRoute } from '$lib/constants';
-import { redirect } from '@sveltejs/kit';
 import type { OAuthConfigResponseDto } from '@api';
+import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
 
 export const load = (async ({ locals: { api } }) => {
-	const { data } = await api.userApi.getUserCount({ admin: true });
-	if (data.userCount === 0) {
-		// Admin not registered
-		throw redirect(302, AppRoute.AUTH_REGISTER);
-	}
+  const { data } = await api.userApi.getUserCount({ admin: true });
+  if (data.userCount === 0) {
+    // Admin not registered
+    throw redirect(302, AppRoute.AUTH_REGISTER);
+  }
 
-	let authConfig: OAuthConfigResponseDto = {
-		passwordLoginEnabled: true,
-		enabled: false
-	};
+  let authConfig: OAuthConfigResponseDto = {
+    passwordLoginEnabled: true,
+    enabled: false,
+  };
 
-	try {
-		// TODO: Figure out how to get correct redirect URI server-side.
-		const { data } = await api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri: '/' } });
-		data.url = undefined;
+  try {
+    // TODO: Figure out how to get correct redirect URI server-side.
+    const { data } = await api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri: '/' } });
+    data.url = undefined;
 
-		authConfig = data;
-	} catch (err) {
-		console.error('[ERROR] login/+page.server.ts:', err);
-	}
+    authConfig = data;
+  } catch (err) {
+    console.error('[ERROR] login/+page.server.ts:', err);
+  }
 
-	return {
-		authConfig,
-		meta: {
-			title: 'Login'
-		}
-	};
+  return {
+    authConfig,
+    meta: {
+      title: 'Login',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte
index 50e4ea22d4..e958864fb2 100644
--- a/web/src/routes/auth/login/+page.svelte
+++ b/web/src/routes/auth/login/+page.svelte
@@ -1,23 +1,23 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import LoginForm from '$lib/components/forms/login-form.svelte';
-	import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
-	import { AppRoute } from '$lib/constants';
-	import { loginPageMessage } from '$lib/constants';
-	import type { PageData } from './$types';
+  import { goto } from '$app/navigation';
+  import LoginForm from '$lib/components/forms/login-form.svelte';
+  import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
+  import { AppRoute } from '$lib/constants';
+  import { loginPageMessage } from '$lib/constants';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 </script>
 
 <FullscreenContainer title={data.meta.title} showMessage={!!loginPageMessage}>
-	<p slot="message">
-		<!-- eslint-disable-next-line svelte/no-at-html-tags -->
-		{@html loginPageMessage}
-	</p>
+  <p slot="message">
+    <!-- eslint-disable-next-line svelte/no-at-html-tags -->
+    {@html loginPageMessage}
+  </p>
 
-	<LoginForm
-		authConfig={data.authConfig}
-		on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })}
-		on:first-login={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)}
-	/>
+  <LoginForm
+    authConfig={data.authConfig}
+    on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })}
+    on:first-login={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)}
+  />
 </FullscreenContainer>
diff --git a/web/src/routes/auth/logout/+server.ts b/web/src/routes/auth/logout/+server.ts
index a02b915388..dff2627b8f 100644
--- a/web/src/routes/auth/logout/+server.ts
+++ b/web/src/routes/auth/logout/+server.ts
@@ -1,12 +1,12 @@
-import { json } from '@sveltejs/kit';
 import { api } from '@api';
 import type { RequestHandler } from '@sveltejs/kit';
+import { json } from '@sveltejs/kit';
 
 export const POST = (async ({ cookies }) => {
-	api.removeAccessToken();
+  api.removeAccessToken();
 
-	cookies.delete('immich_auth_type', { path: '/' });
-	cookies.delete('immich_access_token', { path: '/' });
+  cookies.delete('immich_auth_type', { path: '/' });
+  cookies.delete('immich_access_token', { path: '/' });
 
-	return json({ ok: true });
+  return json({ ok: true });
 }) satisfies RequestHandler;
diff --git a/web/src/routes/auth/register/+page.server.ts b/web/src/routes/auth/register/+page.server.ts
index 3b564e3cd3..85b4d9bc72 100644
--- a/web/src/routes/auth/register/+page.server.ts
+++ b/web/src/routes/auth/register/+page.server.ts
@@ -1,17 +1,17 @@
+import { AppRoute } from '$lib/constants';
 import { redirect } from '@sveltejs/kit';
 import type { PageServerLoad } from './$types';
-import { AppRoute } from '$lib/constants';
 
 export const load = (async ({ locals: { api } }) => {
-	const { data } = await api.userApi.getUserCount({ admin: true });
-	if (data.userCount != 0) {
-		// Admin has been registered, redirect to login
-		throw redirect(302, AppRoute.AUTH_LOGIN);
-	}
+  const { data } = await api.userApi.getUserCount({ admin: true });
+  if (data.userCount != 0) {
+    // Admin has been registered, redirect to login
+    throw redirect(302, AppRoute.AUTH_LOGIN);
+  }
 
-	return {
-		meta: {
-			title: 'Admin Registration'
-		}
-	};
+  return {
+    meta: {
+      title: 'Admin Registration',
+    },
+  };
 }) satisfies PageServerLoad;
diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte
index a711e28771..c5db69f043 100644
--- a/web/src/routes/auth/register/+page.svelte
+++ b/web/src/routes/auth/register/+page.svelte
@@ -1,16 +1,16 @@
 <script lang="ts">
-	import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
-	import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
-	import type { PageData } from './$types';
+  import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
+  import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
+  import type { PageData } from './$types';
 
-	export let data: PageData;
+  export let data: PageData;
 </script>
 
 <FullscreenContainer title={data.meta.title}>
-	<p slot="message">
-		Since you are the first user on the system, you will be assigned as the Admin and are
-		responsible for administrative tasks, and additional users will be created by you.
-	</p>
+  <p slot="message">
+    Since you are the first user on the system, you will be assigned as the Admin and are responsible for administrative
+    tasks, and additional users will be created by you.
+  </p>
 
-	<AdminRegistrationForm />
+  <AdminRegistrationForm />
 </FullscreenContainer>
diff --git a/web/src/test-data/factories/album-factory.ts b/web/src/test-data/factories/album-factory.ts
index fc0861d215..1ea7284448 100644
--- a/web/src/test-data/factories/album-factory.ts
+++ b/web/src/test-data/factories/album-factory.ts
@@ -4,15 +4,15 @@ import { Sync } from 'factory.ts';
 import { userFactory } from './user-factory';
 
 export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
-	albumName: Sync.each(() => faker.commerce.product()),
-	albumThumbnailAssetId: null,
-	assetCount: Sync.each((i) => i % 5),
-	assets: [],
-	createdAt: Sync.each(() => faker.date.past().toISOString()),
-	updatedAt: Sync.each(() => faker.date.past().toISOString()),
-	id: Sync.each(() => faker.datatype.uuid()),
-	ownerId: Sync.each(() => faker.datatype.uuid()),
-	owner: userFactory.build(),
-	shared: false,
-	sharedUsers: []
+  albumName: Sync.each(() => faker.commerce.product()),
+  albumThumbnailAssetId: null,
+  assetCount: Sync.each((i) => i % 5),
+  assets: [],
+  createdAt: Sync.each(() => faker.date.past().toISOString()),
+  updatedAt: Sync.each(() => faker.date.past().toISOString()),
+  id: Sync.each(() => faker.datatype.uuid()),
+  ownerId: Sync.each(() => faker.datatype.uuid()),
+  owner: userFactory.build(),
+  shared: false,
+  sharedUsers: [],
 });
diff --git a/web/src/test-data/factories/user-factory.ts b/web/src/test-data/factories/user-factory.ts
index 79380b0619..ebf280159e 100644
--- a/web/src/test-data/factories/user-factory.ts
+++ b/web/src/test-data/factories/user-factory.ts
@@ -3,17 +3,17 @@ import { faker } from '@faker-js/faker';
 import { Sync } from 'factory.ts';
 
 export const userFactory = Sync.makeFactory<UserResponseDto>({
-	id: Sync.each(() => faker.datatype.uuid()),
-	email: Sync.each(() => faker.internet.email()),
-	firstName: Sync.each(() => faker.name.firstName()),
-	lastName: Sync.each(() => faker.name.lastName()),
-	storageLabel: Sync.each(() => faker.random.alphaNumeric()),
-	externalPath: Sync.each(() => faker.random.alphaNumeric()),
-	profileImagePath: '',
-	shouldChangePassword: Sync.each(() => faker.datatype.boolean()),
-	isAdmin: true,
-	createdAt: Sync.each(() => faker.date.past().toISOString()),
-	deletedAt: null,
-	updatedAt: Sync.each(() => faker.date.past().toISOString()),
-	oauthId: ''
+  id: Sync.each(() => faker.datatype.uuid()),
+  email: Sync.each(() => faker.internet.email()),
+  firstName: Sync.each(() => faker.name.firstName()),
+  lastName: Sync.each(() => faker.name.lastName()),
+  storageLabel: Sync.each(() => faker.random.alphaNumeric()),
+  externalPath: Sync.each(() => faker.random.alphaNumeric()),
+  profileImagePath: '',
+  shouldChangePassword: Sync.each(() => faker.datatype.boolean()),
+  isAdmin: true,
+  createdAt: Sync.each(() => faker.date.past().toISOString()),
+  deletedAt: null,
+  updatedAt: Sync.each(() => faker.date.past().toISOString()),
+  oauthId: '',
 });
diff --git a/web/svelte.config.js b/web/svelte.config.js
index 283f010154..1bb1dfa139 100644
--- a/web/svelte.config.js
+++ b/web/svelte.config.js
@@ -1,13 +1,13 @@
-import preprocess from 'svelte-preprocess';
 import adapter from '@sveltejs/adapter-node';
+import preprocess from 'svelte-preprocess';
 
 /** @type {import('@sveltejs/kit').Config} */
 const config = {
-	preprocess: preprocess(),
+  preprocess: preprocess(),
 
-	kit: {
-		adapter: adapter({ out: 'build' })
-	}
+  kit: {
+    adapter: adapter({ out: 'build' }),
+  },
 };
 
 export default config;
diff --git a/web/tailwind.config.cjs b/web/tailwind.config.cjs
index d1ed4c5f36..acb5bae30f 100644
--- a/web/tailwind.config.cjs
+++ b/web/tailwind.config.cjs
@@ -1,29 +1,29 @@
 /** @type {import('tailwindcss').Config} */
 module.exports = {
-	content: ['./src/**/*.{html,js,svelte,ts}'],
-	darkMode: 'class',
-	theme: {
-		extend: {
-			colors: {
-				// Light Theme
-				'immich-primary': '#4250af',
-				'immich-bg': 'white',
-				'immich-fg': 'black',
-				'immich-gray': '#F6F6F4',
+  content: ['./src/**/*.{html,js,svelte,ts}'],
+  darkMode: 'class',
+  theme: {
+    extend: {
+      colors: {
+        // Light Theme
+        'immich-primary': '#4250af',
+        'immich-bg': 'white',
+        'immich-fg': 'black',
+        'immich-gray': '#F6F6F4',
 
-				// Dark Theme
-				'immich-dark-primary': '#adcbfa',
-				'immich-dark-bg': 'black',
-				'immich-dark-fg': '#e5e7eb',
-				'immich-dark-gray': '#212121'
-			},
-			fontFamily: {
-				'immich-title': ['Snowburst One', 'cursive']
-			},
-			spacing: {
-				18: '4.5rem'
-			}
-		}
-	},
-	plugins: []
+        // Dark Theme
+        'immich-dark-primary': '#adcbfa',
+        'immich-dark-bg': 'black',
+        'immich-dark-fg': '#e5e7eb',
+        'immich-dark-gray': '#212121',
+      },
+      fontFamily: {
+        'immich-title': ['Snowburst One', 'cursive'],
+      },
+      spacing: {
+        18: '4.5rem',
+      },
+    },
+  },
+  plugins: [],
 };
diff --git a/web/vite.config.js b/web/vite.config.js
index 474a4219d5..cc520062ad 100644
--- a/web/vite.config.js
+++ b/web/vite.config.js
@@ -3,26 +3,26 @@ import path from 'path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
-	resolve: {
-		alias: {
-			'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js',
-			'@api': path.resolve('./src/api')
-		}
-	},
-	server: {
-		// connect to a remote backend during web-only development
-		proxy: {
-			'/api': {
-				target: process.env.PUBLIC_IMMICH_SERVER_URL,
-				secure: true,
-				changeOrigin: true,
-				logLevel: 'debug',
-				rewrite: (path) => path.replace(/^\/api/, ''),
-				ws: true
-			}
-		}
-	},
-	plugins: [sveltekit()]
+  resolve: {
+    alias: {
+      'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js',
+      '@api': path.resolve('./src/api'),
+    },
+  },
+  server: {
+    // connect to a remote backend during web-only development
+    proxy: {
+      '/api': {
+        target: process.env.PUBLIC_IMMICH_SERVER_URL,
+        secure: true,
+        changeOrigin: true,
+        logLevel: 'debug',
+        rewrite: (path) => path.replace(/^\/api/, ''),
+        ws: true,
+      },
+    },
+  },
+  plugins: [sveltekit()],
 };
 
 export default config;