diff --git a/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue b/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue
index b986d25bc9..8100571a9a 100644
--- a/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue
+++ b/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue
@@ -1,6 +1,6 @@
+
diff --git a/packages/api-client/src/modules/labrinth/oauth/internal.ts b/packages/api-client/src/modules/labrinth/oauth/internal.ts
index 0b44186320..680009e05e 100644
--- a/packages/api-client/src/modules/labrinth/oauth/internal.ts
+++ b/packages/api-client/src/modules/labrinth/oauth/internal.ts
@@ -45,14 +45,15 @@ export class LabrinthOAuthInternalModule extends AbstractModule {
* @returns Promise resolving to an array of OAuth clients
*/
public async getApps(ids: string[]): Promise {
- return this.client.request(
- `/oauth/apps?ids=${encodeURIComponent(JSON.stringify(ids))}`,
- {
- api: 'labrinth',
- version: 'internal',
- method: 'GET',
- },
- )
+ if (ids.length === 0) {
+ return []
+ }
+
+ // bulk `/oauth/apps` is broken on backend, fetch by id instead
+ // TODO: Remove this once the backend is fixed
+ const results = await Promise.all(ids.map((id) => this.getApp(id).catch(() => null)))
+
+ return results.filter((app): app is Labrinth.OAuth.Internal.OAuthClient => app !== null)
}
/**
diff --git a/packages/app-lib/src/state/settings.rs b/packages/app-lib/src/state/settings.rs
index cd7d4615f5..011778996e 100644
--- a/packages/app-lib/src/state/settings.rs
+++ b/packages/app-lib/src/state/settings.rs
@@ -329,6 +329,7 @@ pub enum Theme {
Dark,
Light,
Oled,
+ Retro,
System,
}
@@ -338,6 +339,7 @@ impl Theme {
Theme::Dark => "dark",
Theme::Light => "light",
Theme::Oled => "oled",
+ Theme::Retro => "retro",
Theme::System => "system",
}
}
@@ -347,6 +349,7 @@ impl Theme {
"dark" => Theme::Dark,
"light" => Theme::Light,
"oled" => Theme::Oled,
+ "retro" => Theme::Retro,
"system" => Theme::System,
_ => Theme::Dark,
}
diff --git a/packages/assets/styles/variables.scss b/packages/assets/styles/variables.scss
index fdcb7bbabd..43bc28aa50 100644
--- a/packages/assets/styles/variables.scss
+++ b/packages/assets/styles/variables.scss
@@ -422,6 +422,39 @@ html {
}
.retro-mode {
+ @extend .dark-mode;
+ --surface-1: #191917;
+ --surface-2: rgb(22, 22, 21);
--surface-2-5: #3a3c3e;
+ --surface-3: #232421;
+ --surface-4: #3a3b38;
+ --surface-5: #5a5c58;
+ --color-button-bg: #3a3b38;
+ --color-base: #c3c4b3;
+ --color-secondary: #9b9e98;
+ --color-contrast: #e6e2d1;
+
+ --color-brand: #4d9227;
+ --color-brand-highlight: #25421e;
+ --color-accent-contrast: #ffffff;
+ --color-ad: var(--color-brand-highlight);
+ --color-ad-raised: var(--color-brand);
+ --color-ad-contrast: black;
+ --color-ad-highlight: var(--color-brand);
+
+ --color-red: rgb(232, 32, 13);
+ --color-orange: rgb(232, 141, 13);
+ --color-green: rgb(60, 219, 54);
+ --color-blue: rgb(9, 159, 239);
+ --color-purple: rgb(139, 129, 230);
+ --color-gray: #718096;
+
+ --color-red-highlight: rgba(232, 32, 13, 0.25);
+ --color-orange-highlight: rgba(232, 141, 13, 0.25);
+ --color-green-highlight: rgba(60, 219, 54, 0.25);
+ --color-blue-highlight: rgba(9, 159, 239, 0.25);
+ --color-purple-highlight: rgba(139, 129, 230, 0.25);
+ --color-gray-highlight: rgba(113, 128, 150, 0.25);
+
--brand-gradient-strong-bg: #3a3b38;
}
diff --git a/packages/ui/src/layouts/shared/user-profile/layout.vue b/packages/ui/src/layouts/shared/user-profile/layout.vue
index 28dcdf0f8d..6ba0cdf85d 100644
--- a/packages/ui/src/layouts/shared/user-profile/layout.vue
+++ b/packages/ui/src/layouts/shared/user-profile/layout.vue
@@ -402,7 +402,11 @@ import {
SpinnerIcon,
XIcon,
} from '@modrinth/assets'
-import { UserBadge } from '@modrinth/utils'
+import {
+ isModrinthUser as checkIsModrinthUser,
+ isOfficialAccount as checkIsOfficialAccount,
+ UserBadge,
+} from '@modrinth/utils'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
@@ -716,8 +720,8 @@ const earliestProjectByType = computed(() => {
return earliest
})
-const isModrinthUser = computed(() => user.value?.id === '2REoufqX')
-const isOfficialAccount = computed(() => isModrinthUser.value || user.value?.id === 'GVFjtWTf')
+const isModrinthUser = computed(() => checkIsModrinthUser(user.value?.id))
+const isOfficialAccount = computed(() => checkIsOfficialAccount(user.value?.id))
const isSelf = computed(() => auth.user.value?.id === user.value?.id)
const isAdminViewing = computed(() => auth.user.value?.role === 'admin')
const isStaffViewing = computed(
diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json
index f515082ecb..f413e28eaf 100644
--- a/packages/ui/src/locales/en-US/index.json
+++ b/packages/ui/src/locales/en-US/index.json
@@ -2318,6 +2318,9 @@
"label.password": {
"defaultMessage": "Password"
},
+ "label.permissions": {
+ "defaultMessage": "Permissions"
+ },
"label.plan-custom": {
"defaultMessage": "Custom"
},
diff --git a/packages/ui/src/utils/common-messages.ts b/packages/ui/src/utils/common-messages.ts
index f9d65d0747..26eefd90ad 100644
--- a/packages/ui/src/utils/common-messages.ts
+++ b/packages/ui/src/utils/common-messages.ts
@@ -287,6 +287,10 @@ export const commonMessages = defineMessages({
id: 'label.scopes',
defaultMessage: 'Scopes',
},
+ permissionsLabel: {
+ id: 'label.permissions',
+ defaultMessage: 'Permissions',
+ },
searchLabel: {
id: 'label.search',
defaultMessage: 'Search',
diff --git a/packages/utils/users.ts b/packages/utils/users.ts
index f13f438d81..192955dd9d 100644
--- a/packages/utils/users.ts
+++ b/packages/utils/users.ts
@@ -13,3 +13,17 @@ export const isAdmin = (user) => {
}
export const STAFF_ROLES = ['moderator', 'admin']
+
+export const MODRINTH_USER_ID = '2REoufqX'
+export const AUTOMOD_USER_ID = ''
+export const MODRINTH_ARCHIVES_USER_ID = 'GVFjtWTf'
+
+export const OFFICIAL_ACCOUNT_IDS = [MODRINTH_USER_ID, AUTOMOD_USER_ID, MODRINTH_ARCHIVES_USER_ID]
+
+export const isModrinthUser = (userId) => {
+ return userId === MODRINTH_USER_ID
+}
+
+export const isOfficialAccount = (userId) => {
+ return OFFICIAL_ACCOUNT_IDS.includes(userId)
+}