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