Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import IncompatibilityWarningModal from '@/components/ui/install_flow/Incompatib
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue'
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
import NavButton from '@/components/ui/NavButton.vue'
Expand Down Expand Up @@ -388,6 +389,7 @@ loading.setEnabled(false)

const error = useError()
const errorModal = ref()
const minecraftAuthErrorModal = ref()

const install = useInstall()
const modInstallModal = ref()
Expand Down Expand Up @@ -466,6 +468,7 @@ onMounted(() => {
invoke('show_window')

error.setErrorModal(errorModal.value)
error.setMinecraftAuthErrorModal(minecraftAuthErrorModal.value)

install.setIncompatibilityWarningModal(incompatibilityWarningModal)
install.setInstallConfirmModal(installConfirmModal)
Expand Down Expand Up @@ -1131,6 +1134,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<I18nDebugPanel />
<NotificationPanel has-sidebar />
<ErrorModal ref="errorModal" />
<MinecraftAuthErrorModal ref="minecraftAuthErrorModal" />
<ModInstallModal ref="modInstallModal" />
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
<InstallConfirmModal ref="installConfirmModal" />
Expand Down
1 change: 1 addition & 0 deletions apps/app-frontend/src/components/ui/ErrorModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const metadata = ref({})
defineExpose({
async show(errorVal, context, canClose = true, source = null) {
console.log(errorVal, context, canClose, source)
closable.value = canClose
if (errorVal.message && errorVal.message.includes('Minecraft authentication error:')) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<script setup lang="ts">
import {
CheckIcon,
CopyIcon,
DropdownIcon,
LogInIcon,
MessagesSquareIcon,
WrenchIcon,
} from '@modrinth/assets'
import { Admonition, ButtonStyled, Collapsible, NewModal } from '@modrinth/ui'
import { computed, ref } from 'vue'

import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { handleSevereError } from '@/store/error.js'

import { type MinecraftAuthError, minecraftAuthErrors } from './minecraft-auth-errors'

const modal = ref<InstanceType<typeof NewModal>>()
const rawError = ref<string>('')
const matchedError = ref<MinecraftAuthError | null>(null)
const debugCollapsed = ref(true)
const copied = ref(false)
const loadingSignIn = ref(false)

function show(errorVal: { message?: string }) {
rawError.value = errorVal?.message ?? String(errorVal)

matchedError.value = minecraftAuthErrors.find((e) => rawError.value.includes(e.errorCode)) ?? null

debugCollapsed.value = true
modal.value?.show()
}

function hide() {
modal.value?.hide()
}

defineExpose({
show,
hide,
})

async function signInAgain() {
try {
loadingSignIn.value = true
const loggedIn = await login_flow()
if (loggedIn) {
await set_default_user(loggedIn.profile.id)
}
loadingSignIn.value = false
modal.value?.hide()
} catch (err) {
loadingSignIn.value = false
handleSevereError(err)
}
}

const debugInfo = computed(() => rawError.value || 'No error message.')

async function copyToClipboard(text: string) {
await navigator.clipboard.writeText(text)
copied.value = true
setTimeout(() => {
copied.value = false
}, 3000)
}
</script>

<template>
<NewModal ref="modal" header="Sign in Failed" :max-width="'548px'">
<div class="flex flex-col gap-6">
<Admonition
type="warning"
body=" We couldn't sign you into your Microsoft account. This may be due to account restrictions or
regional limitations."
>
</Admonition>

<!-- Matched error details -->
<div class="bg-surface-2 rounded-2xl p-4 px-5 flex flex-col gap-3">
<template v-if="matchedError">
<div class="flex flex-col gap-1.5">
<h3 class="text-base font-bold m-0">What we think happened</h3>
<p class="text-sm text-secondary m-0">
{{ matchedError.whatHappened }}
</p>
</div>

<div class="flex flex-col gap-1.5">
<h3 class="text-base font-bold m-0">How to fix it</h3>
<ol class="list-none flex flex-col gap-2 m-0 pl-0">
<li
v-for="(step, index) in matchedError.stepsToFix"
:key="index"
class="flex items-baseline gap-2"
>
<span
class="inline-flex items-center justify-center shrink-0 w-5 h-5 rounded-full bg-surface-4 border border-solid border-surface-5 text-xs font-medium"
>
{{ index + 1 }}
</span>
<!-- eslint-disable-next-line vue/no-v-html -->
<span
class="text-sm [&_a]:text-info [&_a]:font-medium [&_a]:underline"
v-html="step"
/>
</li>
</ol>
</div>
</template>
<template v-else>
<div class="flex flex-col gap-1.5">
<h3 class="text-base font-bold m-0">Unknown error</h3>
<p class="text-sm text-secondary m-0">
We don’t recognize this error and can’t recommend specific steps to resolve it.
</p>
<p class="text-sm text-secondary m-0">
Try visiting
<a
class="text-info font-medium underline hover:underline"
href="https://www.minecraft.net/en-us/login"
>Minecraft Login</a
>
and signing in, as it may prompt you with the necessary steps. You can also contact
support and we can look into it further.
</p>
</div>
</template>
</div>

<!-- Action buttons -->
<div class="flex items-center gap-2">
<ButtonStyled>
<a href="https://support.modrinth.com" class="!w-full" @click="modal?.hide()">
<MessagesSquareIcon /> Contact support
</a>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="loadingSignIn" class="!w-full" @click="signInAgain">
<LogInIcon /> Sign in again
</button>
</ButtonStyled>
</div>

<div class="flex flex-col gap-2">
<div class="w-full h-[1px] bg-surface-5"></div>

<!-- Debug info -->
<div class="overflow-clip">
<button
class="flex items-center justify-between w-full bg-transparent border-0 py-4 cursor-pointer"
@click="debugCollapsed = !debugCollapsed"
>
<span class="flex items-center gap-2 text-contrast font-extrabold m-0">
<WrenchIcon class="h-4 w-4" />
Debug information
</span>
<DropdownIcon
class="h-5 w-5 text-secondary transition-transform"
:class="{ 'rotate-180': !debugCollapsed }"
/>
</button>
<Collapsible :collapsed="debugCollapsed">
<div class="p-3 bg-surface-2 rounded-2xl text-xs flex items-start">
<div class="m-0 p-0 rounded-none bg-transparent text-sm font-mono">
{{ debugInfo }}
</div>
<ButtonStyled circular>
<button
v-tooltip="'Copy debug info'"
:disabled="copied"
@click="copyToClipboard(debugInfo)"
>
<template v-if="copied"> <CheckIcon class="text-green" /> </template>
<template v-else> <CopyIcon /> </template>
</button>
</ButtonStyled>
</div>
</Collapsible>
</div>
</div>
</div>
</NewModal>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
export interface MinecraftAuthError {
errorCode: string
whatHappened: string
stepsToFix: string[]
}

export const minecraftAuthErrors: MinecraftAuthError[] = [
{
errorCode: '2148916222',
whatHappened:
'Your Minecraft/Xbox Live account requires age verification to comply with UK regulations. You must complete this before signing in.',
stepsToFix: [
'Go to the <a href="https://www.minecraft.net/en-us/login">Minecraft Login</a> page and sign in',
'Follow the instructions to verify your age',
'Once verified, try signing in again',
'For additional help, visit <a href="https://support.xbox.com/en-GB/help/family-online-safety/online-safety/UK-age-verification">UK age verification on Xbox</a>',
],
},
{
errorCode: '2148916233',
whatHappened: "This account doesn't have an Xbox profile set up or doesn't own Minecraft.",
stepsToFix: [
'Make sure Minecraft is purchased on this account',
'Visit <a href="https://www.minecraft.net/en-us/login">Minecraft Login</a> and sign in',
'Complete Xbox profile setup if prompted',
'Once finished, try signing in again',
],
},
{
errorCode: '2148916235',
whatHappened: "Xbox Live isn't available in your region, so sign-in is blocked.",
stepsToFix: [
'Xbox services must be supported in your country before you can sign in',
'Check <a href="https://www.xbox.com/en-US/regions">Xbox Availability</a> for supported regions',
],
},
{
errorCode: '2148916236',
whatHappened: 'This account requires adult verification under South Korean regulations.',
stepsToFix: [
'Visit <a href="https://www.xbox.com">Xbox</a> and sign in',
'Complete the identity verification process',
'Once finished, try signing in again',
],
},
{
errorCode: '2148916237',
whatHappened: 'This account requires adult verification under South Korean regulations.',
stepsToFix: [
'Visit <a href="https://www.xbox.com">Xbox</a> and sign in',
'Complete the identity verification process',
'Once finished, try signing in again',
],
},
{
errorCode: '2148916238',
whatHappened: 'This account is underage and not linked to a Microsoft family group.',
stepsToFix: [
'Review the <a href="https://help.minecraft.net/hc/en-us/articles/4408968616077">Family Setup Guide</a>',
'Join or create a family group as instructed',
'Once finished, try signing in again',
],
},
{
errorCode: '2148916227',
whatHappened: 'This account was suspended for violating Xbox Community Standards.',
stepsToFix: [
'Visit <a href="https://support.xbox.com">Xbox Support</a> and review the enforcement details',
'Submit an appeal if one is available',
],
},
{
errorCode: '2148916229',
whatHappened: "This account is restricted and doesn't have permission to play online.",
stepsToFix: [
'Have a guardian sign in to <a href="https://account.microsoft.com/family/">Microsoft Family</a>',
'Update online play permissions',
'Once finished, try signing in again',
],
},
{
errorCode: '2148916234',
whatHappened: "This account hasn't accepted Xbox's Terms of Service.",
stepsToFix: [
'Visit <a href="https://www.xbox.com">Xbox</a> and sign in',
'Accept the Terms if prompted',
'Once finished, try signing in again',
],
},
]
12 changes: 12 additions & 0 deletions apps/app-frontend/src/store/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ import { defineStore } from 'pinia'
export const useError = defineStore('errorsStore', {
state: () => ({
errorModal: null,
minecraftAuthErrorModal: null,
}),
actions: {
setErrorModal(ref) {
this.errorModal = ref
},
setMinecraftAuthErrorModal(ref) {
this.minecraftAuthErrorModal = ref
},
showError(error, context, closable = true, source = null) {
if (
error.message &&
error.message.includes('Minecraft authentication error:') &&
this.minecraftAuthErrorModal
) {
this.minecraftAuthErrorModal.show(error)
return
}
this.errorModal.show(error, context, closable, source)
},
},
Expand Down
2 changes: 2 additions & 0 deletions packages/assets/generated-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import _ManageIcon from './icons/manage.svg?component'
import _MaximizeIcon from './icons/maximize.svg?component'
import _MemoryStickIcon from './icons/memory-stick.svg?component'
import _MessageIcon from './icons/message.svg?component'
import _MessagesSquareIcon from './icons/messages-square.svg?component'
import _MicrophoneIcon from './icons/microphone.svg?component'
import _MinimizeIcon from './icons/minimize.svg?component'
import _MinusIcon from './icons/minus.svg?component'
Expand Down Expand Up @@ -466,6 +467,7 @@ export const ManageIcon = _ManageIcon
export const MaximizeIcon = _MaximizeIcon
export const MemoryStickIcon = _MemoryStickIcon
export const MessageIcon = _MessageIcon
export const MessagesSquareIcon = _MessagesSquareIcon
export const MicrophoneIcon = _MicrophoneIcon
export const MinimizeIcon = _MinimizeIcon
export const MinusIcon = _MinusIcon
Expand Down
16 changes: 16 additions & 0 deletions packages/assets/icons/messages-square.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.