Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c8a586c
Backend routes for choosing username in OAuth flow
aecsocket Apr 12, 2026
64f8755
fix up oauth flow routes
aecsocket Apr 13, 2026
fe3aba5
improve URL-related OAuth code
aecsocket Apr 14, 2026
7ea0635
Use user-provided callback addr instead of SELF_ADDR
aecsocket Apr 14, 2026
7484afa
Revert "Use user-provided callback addr instead of SELF_ADDR"
aecsocket Apr 14, 2026
66f3c39
fix flow
aecsocket Apr 14, 2026
57b4f21
fix: backend response for create oauth account
tdgao Apr 17, 2026
c54906e
Merge branch 'main' into boris/dev-908-backend-changes
tdgao Apr 17, 2026
c19ce11
feat: new auth flow (#5840)
tdgao Apr 22, 2026
bdc2b4a
Merge branch 'main' into boris/dev-908-backend-changes
tdgao Apr 22, 2026
52a972d
refactor: pnpm prepr
tdgao Apr 22, 2026
aac61dc
fix: make sure staging uses staging
clrxbl Apr 24, 2026
547921b
fix: hcaptcha styles
tdgao Apr 24, 2026
ed53fe5
fix: copy
tdgao Apr 24, 2026
3bfe670
remove: auth/welcome page as its no longer used
tdgao Apr 24, 2026
042dde0
refactor: bring root page card styles into individual components and …
tdgao Apr 24, 2026
71c1deb
fix: account settings modals to use new modal and fix lots of bad styles
tdgao Apr 24, 2026
c99564f
refactor: pnpm prepr
tdgao Apr 24, 2026
f1ce310
feat: implement last signed in indicator
tdgao Apr 24, 2026
8946e4c
fix: append number when generated name from email is taken
tdgao Apr 24, 2026
4183702
refactor: pnpm prepr
tdgao Apr 24, 2026
fcab720
fix: last sign in badge color
tdgao Apr 24, 2026
4389315
fix: qa issues
tdgao Apr 24, 2026
abcac5f
refactor: pnpm prepr
tdgao Apr 24, 2026
65bd893
fix: hover effect on native date picker
tdgao Apr 24, 2026
cad6bd4
chore: temp staging undo
IMB11 Apr 24, 2026
3ccb813
Merge branch 'boris/dev-908-backend-changes' of https://github.com/mo…
IMB11 Apr 24, 2026
ece35f9
Revert "chore: temp staging undo"
IMB11 Apr 24, 2026
6f2e04c
feat: handle app create account
tdgao Apr 24, 2026
0a0b192
Merge branch 'boris/dev-908-backend-changes' of github.com:modrinth/c…
tdgao Apr 24, 2026
918c4a9
fix: last signed in style
tdgao Apr 24, 2026
652a9b0
fix: add initOnMounted for SSR race
tdgao Apr 24, 2026
9646083
refactor: use typescript
tdgao Apr 24, 2026
f4d6543
refactor: pnpm prepr
tdgao Apr 24, 2026
4ce82b0
Merge branch 'main' into boris/dev-908-backend-changes
IMB11 Apr 27, 2026
3ce8f85
refactor: use typescript for reset-password
tdgao Apr 27, 2026
ec7c893
refactor: convert verify-email to use typescript
tdgao Apr 27, 2026
44da0ed
refactor: convert authorize.vue to use typescript
tdgao Apr 27, 2026
872dcbc
fix: authorize.vue error states
tdgao Apr 27, 2026
d6356ea
feat: small style updates
tdgao Apr 27, 2026
e974672
feat: implement date picker component
tdgao Apr 27, 2026
f32f732
feat: improve UX and styles for range select
tdgao Apr 27, 2026
a5be011
refactor: pnpm prepr
tdgao Apr 27, 2026
b9be6ab
fix: range select border styles
tdgao Apr 27, 2026
2aca44b
feat: implement date picker component in create account
tdgao Apr 27, 2026
62f20b9
feat: implement preserve date for date picker
tdgao Apr 27, 2026
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
2 changes: 1 addition & 1 deletion .github/instructions/i18n-convert.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ Please follow these rules precisely:
Use existing patterns from our codebase:

- Variables/plurals: see `apps/frontend/src/pages/frog.vue`
- Rich-text link tags: see `apps/frontend/src/pages/auth/welcome.vue` and `apps/frontend/src/error.vue`
- Rich-text link tags: see `apps/frontend/src/error.vue`

When you finish, there should be no hard-coded English strings left in the template—everything comes from `formatMessage` or `<IntlFormatted>`.
2 changes: 2 additions & 0 deletions apps/frontend/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { I18nDebugPanel, LoadingBar, NotificationPanel } from '@modrinth/ui'

import { setupProviders } from '~/providers/setup.ts'

import { useAuth } from './composables/auth'

const auth = await useAuth()
setupProviders(auth)
</script>
267 changes: 267 additions & 0 deletions apps/frontend/src/components/ui/auth/CreateAccount.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<template>
<div
class="shadow-card mx-auto flex w-full max-w-[28rem] flex-col gap-6 rounded-2xl border border-button-bg bg-surface-3 p-6"
>
<h1
class="mx-auto my-0 flex w-full justify-center text-center text-2xl font-semibold text-contrast"
>
{{ formatMessage(messages.title) }}
</h1>

<section v-if="requiresDob" class="flex flex-col gap-2.5">
<label class="text-md font-semibold text-contrast" for="create-account-dob">
{{ formatMessage(messages.dateOfBirthLabel) }}
</label>
<DatePicker
id="create-account-dob"
v-model="dateOfBirthModel"
wrapper-class="w-full"
min-date="1900-01-01"
:max-date="maxInputDate"
preserve-day
:placeholder="formatMessage(messages.dateOfBirthPlaceholder)"
/>
<div>
{{ formatMessage(messages.over13HelperText) }}
</div>
<Admonition :type="'info'">
<template #header>
<div class="-mb-3 -mt-1 flex flex-col gap-0 text-sm font-normal leading-normal">
<div>
{{ formatMessage(messages.infoPanelText) }}
</div>
<a
class="w-fit text-link underline"
:href="SOURCE_CODE_URL"
target="_blank"
rel="noopener noreferrer"
>
{{ formatMessage(messages.relevantSourceCodeText) }}
</a>
</div>
</template>
</Admonition>
</section>

<section class="flex flex-col gap-2.5">
<label class="text-md font-semibold text-contrast" for="create-account-username">
{{ formatMessage(messages.usernameOptionalLabel) }}
<span class="font-normal text-primary">(optional)</span>
</label>
<StyledInput
id="create-account-username"
v-model="usernameModel"
type="text"
:placeholder="formatMessage(messages.usernamePlaceholder)"
wrapper-class="w-full"
/>
</section>

<section v-if="globals?.captcha_enabled" class="flex flex-col gap-2.5">
<label class="text-md font-semibold text-contrast">{{
formatMessage(messages.securityCheckLabel)
}}</label>
<HCaptcha v-if="globals?.captcha_enabled" :ref="onSetCaptchaRef" v-model="tokenModel" />
</section>

<div
class="flex gap-2.5 rounded-2xl border border-solid border-surface-5 bg-surface-3 transition-all hover:brightness-110"
>
<Checkbox
v-model="subscribeModel"
class="p-3 text-left leading-snug text-primary transition-all"
:label="formatMessage(messages.subscribeLabel)"
:description="formatMessage(messages.subscribeLabel)"
/>
</div>

<ButtonStyled color="brand">
<button
class="!w-full font-bold"
:disabled="globals?.captcha_enabled ? !tokenModel : false"
@click="onCompleteSignUpClick"
>
{{ formatMessage(messages.completeSignUpButton) }}
<RightArrowIcon />
</button>
</ButtonStyled>
</div>
</template>

<script setup lang="ts">
import { RightArrowIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
Checkbox,
DatePicker,
defineMessages,
injectNotificationManager,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { computed } from 'vue'

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

interface AuthGlobals {
captcha_enabled?: boolean
[key: string]: unknown
}

interface Props {
globals?: AuthGlobals | null
requiresDob?: boolean
onCompleteSignUp?: () => void
onSetCaptchaRef?: ((captchaRef: unknown) => void) | undefined
}

const {
globals = null,
requiresDob = true,
onCompleteSignUp = () => {},
onSetCaptchaRef = undefined,
} = defineProps<Props>()

const SOURCE_CODE_URL =
'https://github.com/modrinth/code/blob/main/apps/frontend/src/components/ui/auth/CreateAccount.vue'

const dateOfBirthModel = defineModel<string>('dateOfBirth', { default: '' })
const usernameModel = defineModel<string>('username', { default: '' })
const tokenModel = defineModel<string>('token', { default: '' })
const subscribeModel = defineModel<boolean>('subscribe', { default: false })

const maxInputDate = computed(() => `${new Date().getFullYear()}-12-31`)

const maxBirthDate = computed(() => {
const date = new Date()
date.setFullYear(date.getFullYear() - 13)
return date.toISOString().slice(0, 10)
})

const getBirthYear = (dateOfBirth: string): number | null => {
const [yearPart = ''] = dateOfBirth.split('-')
const year = Number(yearPart)
return Number.isInteger(year) ? year : null
}

const isDateOfBirthMissing = computed(() => requiresDob && dateOfBirthModel.value === '')

const isDateOfBirthYearZero = computed(() => {
if (!requiresDob || dateOfBirthModel.value === '') {
return false
}

return getBirthYear(dateOfBirthModel.value) === 0
})

const isUnder13 = computed(
() => requiresDob && dateOfBirthModel.value !== '' && dateOfBirthModel.value > maxBirthDate.value,
)

const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()

function onCompleteSignUpClick() {
if (isDateOfBirthMissing.value) {
addNotification({
title: formatMessage(messages.dateOfBirthRequiredTitle),
text: formatMessage(messages.dateOfBirthRequiredText),
type: 'warning',
})
return
}

if (isDateOfBirthYearZero.value) {
addNotification({
title: formatMessage(messages.dateOfBirthInvalidTitle),
text: formatMessage(messages.dateOfBirthInvalidText),
type: 'error',
})
return
}

if (isUnder13.value) {
addNotification({
title: formatMessage(messages.ageRequirementWarningTitle),
text: formatMessage(messages.under13HelperText),
type: 'error',
})
return
}

onCompleteSignUp()
}

const messages = defineMessages({
title: {
id: 'auth.create-account.title',
defaultMessage: 'Create an Account',
},
dateOfBirthLabel: {
id: 'auth.create-account.date-of-birth.label',
defaultMessage: 'Date of birth',
},
dateOfBirthPlaceholder: {
id: 'auth.create-account.date-of-birth.placeholder',
defaultMessage: 'Select your date of birth',
},
dateOfBirthRequiredTitle: {
id: 'auth.create-account.date-of-birth.required.title',
defaultMessage: 'Date of birth required',
},
dateOfBirthRequiredText: {
id: 'auth.create-account.date-of-birth.required.text',
defaultMessage: 'Please enter your date of birth before continuing.',
},
dateOfBirthInvalidTitle: {
id: 'auth.create-account.date-of-birth.invalid.title',
defaultMessage: 'Invalid date of birth',
},
dateOfBirthInvalidText: {
id: 'auth.create-account.date-of-birth.invalid.text',
defaultMessage: 'Please enter a valid date of birth. Year cannot be 0000.',
},
over13HelperText: {
id: 'auth.create-account.date-of-birth.over13-helper',
defaultMessage: 'You must be over 13 years old to use Modrinth.',
},
under13HelperText: {
id: 'auth.create-account.date-of-birth.under13-helper',
defaultMessage: 'You cannot create an account at Modrinth unless you are over 13 years old.',
},
ageRequirementWarningTitle: {
id: 'auth.create-account.age-requirement.warning-title',
defaultMessage: 'Age requirement',
},
infoPanelText: {
id: 'auth.create-account.info-panel.text',
defaultMessage:
'We do not store your date of birth, it is only used to confirm your age at sign up.',
},
relevantSourceCodeText: {
id: 'auth.create-account.info-panel.source-code-link',
defaultMessage: 'Relevant source code',
},
usernameOptionalLabel: {
id: 'auth.create-account.username.optional-label',
defaultMessage: 'Username',
},
usernamePlaceholder: {
id: 'auth.create-account.username.placeholder',
defaultMessage: 'Enter username',
},
securityCheckLabel: {
id: 'auth.create-account.security-check.label',
defaultMessage: 'Security check',
},
subscribeLabel: {
id: 'auth.create-account.subscribe.label',
defaultMessage: 'Keep me updated on the cool things Modrinth is working on via email',
},
completeSignUpButton: {
id: 'auth.create-account.complete-sign-up',
defaultMessage: 'Complete sign up',
},
})
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,34 @@ defineExpose({
></div>
</template>

<style lang="scss">
<style>
.h-captcha {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: var(--radius-md);
border: 2px solid var(--color-button-bg);
height: 78px;
height: 100px;
width: 100%;
max-width: 100%;
}

.h-captcha iframe {
transform: scale(1.33);
transform-origin: center;
margin: -1px;
}

@media screen and (max-width: 400px) {
.h-captcha {
height: auto;
}

iframe {
margin: -1px;
.h-captcha iframe {
transform: scale(1.03);
margin: 0;
max-width: 100%;
}
}
</style>
Loading
Loading