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/frontend/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<AdsConsentNotification />
<I18nDebugPanel />
<NuxtPage />
<ClientOnly>
<AuthModal />
</ClientOnly>
<div id="teleports"></div>
</NuxtLayout>
<ServerOnboardingModal
Expand Down Expand Up @@ -38,6 +41,7 @@ import {
import { useWindowScroll } from '@vueuse/core'

import AdsConsentNotification from '~/components/ui/AdsConsentNotification.vue'
import AuthModal from '~/components/ui/auth/AuthModal.vue'
import ServerSharingUpdate from '~/components/ui/servers/ServerSharingUpdate.vue'
import { setupProviders } from '~/providers/setup.ts'

Expand Down
18 changes: 18 additions & 0 deletions apps/frontend/src/components/ui/auth/AuthCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div
class="flex w-full flex-col gap-6 !p-6"
:class="
embedded
? undefined
: 'universal-card mx-auto max-w-[27rem] border border-solid border-surface-5'
"
>
<slot />
</div>
</template>

<script setup lang="ts">
import { isAuthEmbedded } from '@/composables/auth-modal.ts'

const embedded = isAuthEmbedded()
</script>
107 changes: 107 additions & 0 deletions apps/frontend/src/components/ui/auth/AuthModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<template>
<NewModal
ref="modal"
hide-header
merge-header
no-padding
scrollable
width="27rem"
max-width="calc(100vw - 2rem)"
max-content-height="min(40rem, calc(100vh - 4rem))"
:before-hide="allowHide"
:on-hide="onHide"
>
<Suspense>
<component :is="screen" v-if="screen && location" :key="location.fullPath" />
<template #fallback>
<div class="min-h-40" />
</template>
</Suspense>
</NewModal>
</template>

<script setup lang="ts">
import { NewModal } from '@modrinth/ui'
import { type Component, computed, nextTick, onUnmounted, provide, ref, watch } from 'vue'

import ReauthenticateScreen from '@/components/ui/auth/screens/ReauthenticateScreen.vue'
import SignInScreen from '@/components/ui/auth/screens/SignInScreen.vue'
import SignUpScreen from '@/components/ui/auth/screens/SignUpScreen.vue'
import VerifyEmailScreen from '@/components/ui/auth/screens/VerifyEmailScreen.vue'
import {
AUTH_ROUTE_KEY,
authModalLocation,
authModalReady,
type EmbeddableAuthPath,
} from '@/composables/auth-modal.ts'
import { authProviderHandoff } from '@/composables/external-auth.ts'

const screens: Record<EmbeddableAuthPath, Component> = {
'/auth/sign-in': SignInScreen,
'/auth/sign-up': SignUpScreen,
'/auth/reauthenticate': ReauthenticateScreen,
'/auth/verify-email': VerifyEmailScreen,
}

const modal = ref<InstanceType<typeof NewModal>>()
const showing = ref(false)
const route = useRoute()
const location = computed(() => authModalLocation.value)

provide(AUTH_ROUTE_KEY, location)
authModalReady.value = true
onUnmounted(() => {
authModalReady.value = false
})

const screen = computed(() => {
const path = location.value?.path
if (!path || !(path in screens)) {
return null
}
return screens[path as EmbeddableAuthPath]
})

watch(
location,
async (next) => {
if (next) {
if (showing.value) {
return
}
showing.value = true
await nextTick()
modal.value?.show()
return
}
if (!showing.value) {
return
}
showing.value = false
modal.value?.hide()
},
{ immediate: true },
)

watch(
() => route.fullPath,
() => {
if (!authModalLocation.value || authProviderHandoff.value) {
return
}
authModalLocation.value = null
},
)

function allowHide() {
return !authProviderHandoff.value
}

function onHide() {
if (!authModalLocation.value || authProviderHandoff.value) {
return
}
showing.value = false
authModalLocation.value = null
}
</script>
7 changes: 3 additions & 4 deletions apps/frontend/src/components/ui/auth/CreateAccount.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<template>
<div
class="shadow-card mx-auto flex w-full max-w-[27rem] flex-col gap-6 rounded-2xl border border-solid border-button-bg bg-surface-3 !p-6"
>
<AuthCard>
<h1
class="mx-auto my-0 flex w-full justify-center text-center text-2xl font-semibold text-contrast"
>
Expand Down Expand Up @@ -94,7 +92,7 @@
{{ formatMessage(messages.completeSignUpButton) }}
<RightArrowIcon />
</Button>
</div>
</AuthCard>
</template>

<script setup lang="ts">
Expand All @@ -111,6 +109,7 @@ import {
} from '@modrinth/ui'
import { computed, ref } from 'vue'

import AuthCard from '@/components/ui/auth/AuthCard.vue'
import Turnstile from '@/components/ui/auth/Turnstile.vue'

interface AuthGlobals {
Expand Down
8 changes: 4 additions & 4 deletions apps/frontend/src/components/ui/auth/LauncherOpening.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
class="hidden"
:title="formatMessage(messages.launcherCallbackTitle)"
></iframe>
<div
class="universal-card mx-auto flex w-full max-w-[27rem] flex-col gap-6 border border-solid border-surface-5 !p-6 text-center"
>
<AuthCard class="text-center">
<div class="flex flex-col gap-2">
<h1 class="m-0 text-2xl font-semibold text-contrast">
{{ formatMessage(messages.openingLauncherTitle) }}
Expand Down Expand Up @@ -42,14 +40,16 @@
{{ formatMessage(messages.goToWebsiteButton) }}
</ButtonLink>
</div>
</div>
</AuthCard>
</div>
</template>

<script setup lang="ts">
import { RightArrowIcon } from '@modrinth/assets'
import { Button, ButtonLink, defineMessages, useVIntl } from '@modrinth/ui'

import AuthCard from '@/components/ui/auth/AuthCard.vue'

interface Props {
localhostUrl?: string
deeplinkUrl?: string
Expand Down
44 changes: 44 additions & 0 deletions apps/frontend/src/components/ui/auth/OpeningAuthProvider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div class="flex flex-col items-center gap-6 text-center" role="status">
<div class="flex flex-col gap-2">
<h2 class="m-0 text-2xl font-semibold text-contrast">
{{ formatMessage(messages.openingTitle, { provider }) }}
</h2>
<p class="m-0 text-primary">
{{ formatMessage(messages.openingDescription, { provider }) }}
</p>
</div>
<Button class="!w-full !justify-center" @click="emit('cancel')">
{{ formatMessage(messages.signInAnotherWay) }}
</Button>
</div>
</template>

<script setup lang="ts">
import { Button, defineMessages, useVIntl } from '@modrinth/ui'

defineProps<{
provider: string
}>()

const emit = defineEmits<{
cancel: []
}>()

const { formatMessage } = useVIntl()

const messages = defineMessages({
openingTitle: {
id: 'auth.oauth.opening.title',
defaultMessage: 'Opening {provider}...',
},
openingDescription: {
id: 'auth.oauth.opening.description',
defaultMessage: 'Complete your sign in on {provider}',
},
signInAnotherWay: {
id: 'auth.reauthenticate.sign-in-another-way',
defaultMessage: 'Sign in another way',
},
})
</script>
Loading
Loading