feat: minecraft required modal (#6797)

* feat: minecraft required modal

* pnpm prepr

* refactor: move steve to /assets and turn it to webp

* refactor: rename to steve look up left

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Truman Gao
2026-07-21 19:09:34 +00:00
committed by GitHub
co-authored by Prospector
parent 074699be45
commit 7ac1d17571
6 changed files with 182 additions and 18 deletions
@@ -52,10 +52,6 @@ defineExpose({
if (errorVal.message.includes('because the target machine actively refused it')) {
metadata.value.hostsFile = true
}
} else if (errorVal.message && errorVal.message.includes('User is not logged in')) {
title.value = 'Sign in to Minecraft'
errorType.value = 'minecraft_sign_in'
supportLink.value = 'https://support.modrinth.com'
} else if (errorVal.message && errorVal.message.includes('Move directory error:')) {
title.value = 'Could not change app directory'
errorType.value = 'directory_move'
@@ -243,20 +239,6 @@ async function copyToClipboard(text) {
</button>
</div>
</template>
<div v-else-if="errorType === 'minecraft_sign_in'">
<p>
To play this instance, you must sign in through Microsoft below. If you don't have a
Minecraft account, you can purchase the game on the
<a href="https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"
>Minecraft website</a
>.
</p>
<div class="cta-button">
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
<LogInIcon /> Sign in to Minecraft
</button>
</div>
</div>
<template v-else-if="errorType === 'state_init'">
<p>
Modrinth App failed to load correctly. This may be because of a corrupted file, or
@@ -0,0 +1,144 @@
<template>
<NewModal ref="modal" :header="formatMessage(messages.header)" max-width="544px" no-padding>
<div class="grid grid-cols-[1fr_auto] gap-2.5 h-[154px] px-7 pt-4 pb-1 pr-9">
<div class="flex flex-col gap-2.5 items-start justify-center h-min mt-5">
<div class="font-semibold text-xl text-contrast">
{{ formatMessage(messages.descriptionHeader) }}
</div>
<div class="text-secondary leading-6">
{{ formatMessage(messages.description) }}
</div>
</div>
<div class="relative h-full w-[96px] overflow-hidden mx-3">
<div class="absolute top-0 left-0 z-0 w-full flex grow-0 flex-col items-end p-0">
<img :src="steveImage" alt="" class="self-stretch" />
</div>
<div
class="absolute left-0 bottom-0 z-10 order-1 h-6 w-[120px] shrink-0 grow-0 bg-[linear-gradient(180deg,rgba(39,41,46,0)_0%,#27292E_80%,#27292E_100%)]"
></div>
</div>
</div>
<div class="flex flex-col gap-6 px-6 pb-6">
<div class="flex justify-end gap-2">
<ButtonStyled>
<a class="w-full !shadow-none" href="https://support.modrinth.com" @click="modal?.hide()">
<MessagesSquareIcon />
{{ formatMessage(messages.getSupport) }}
</a>
</ButtonStyled>
<ButtonStyled color="brand">
<button class="w-full !shadow-none" :disabled="loadingSignIn" @click="signIn">
<SpinnerIcon v-if="loadingSignIn" class="animate-spin" />
<svg
v-else
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect x="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect y="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect
x="10.75"
y="10.75"
width="9.25"
height="9.25"
fill="black"
fill-opacity="0.9"
/>
</svg>
{{ formatMessage(messages.signIn) }}
</button>
</ButtonStyled>
</div>
<p class="m-0 text-center text-sm text-secondary">
{{ formatMessage(messages.dontHaveAccount) }}
<a
class="text-blue font-medium hover:underline"
href="https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"
>
{{ formatMessage(messages.getMinecraft) }}
</a>
</p>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { MessagesSquareIcon, SpinnerIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
import { inject, type Ref, ref } from 'vue'
import steveImage from '@/assets/steve-look-up-left.webp'
import type AccountsCard from '@/components/ui/AccountsCard.vue'
import { trackEvent } from '@/helpers/analytics'
import { login as loginFlow, set_default_user } from '@/helpers/auth.js'
import { handleSevereError } from '@/store/error.js'
const { formatMessage } = useVIntl()
const accountsCard = inject('accountsCard') as Ref<InstanceType<typeof AccountsCard> | null>
const messages = defineMessages({
header: {
id: 'minecraft-required.header',
defaultMessage: 'Minecraft required',
},
descriptionHeader: {
id: 'minecraft-required.description-header',
defaultMessage: 'Sign in to a Microsoft account',
},
description: {
id: 'minecraft-required.description',
defaultMessage:
'You need a Microsoft account that owns Minecraft before you can launch and play.',
},
getSupport: {
id: 'minecraft-required.get-support',
defaultMessage: 'Get support',
},
signIn: {
id: 'minecraft-required.sign-in',
defaultMessage: 'Sign in to Microsoft',
},
dontHaveAccount: {
id: 'minecraft-required.dont-have-account',
defaultMessage: 'Dont have an account?',
},
getMinecraft: {
id: 'minecraft-required.get-minecraft',
defaultMessage: 'Get Minecraft',
},
})
const modal = ref<InstanceType<typeof NewModal>>()
const loadingSignIn = ref(false)
function show() {
modal.value?.show()
}
async function signIn() {
loadingSignIn.value = true
try {
const loggedIn = await loginFlow()
if (!loggedIn) return
await set_default_user(loggedIn.profile.id)
await accountsCard.value?.refreshValues()
await trackEvent('AccountLogIn', { source: 'MinecraftRequiredModal' })
modal.value?.hide()
} catch (error) {
handleSevereError(error)
} finally {
loadingSignIn.value = false
}
}
defineExpose({
show,
})
</script>