Make settings page localizable (#5294)
* make settings localizable * move plan names to common messages * unknown -> plan-unknown * prepr:frontend
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type MessageDescriptor, useFormatPrice } from '@modrinth/ui'
|
import { type MessageDescriptor, useFormatPrice } from '@modrinth/ui'
|
||||||
import { ButtonStyled, defineMessage, defineMessages, ServersSpecs, useVIntl } from '@modrinth/ui'
|
import {
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessage,
|
||||||
|
defineMessages,
|
||||||
|
ServersSpecs,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatPrice = useFormatPrice()
|
const formatPrice = useFormatPrice()
|
||||||
@@ -37,10 +44,7 @@ const plans: Record<
|
|||||||
buttonColor: 'blue',
|
buttonColor: 'blue',
|
||||||
accentText: 'text-blue',
|
accentText: 'text-blue',
|
||||||
accentBg: 'bg-bg-blue',
|
accentBg: 'bg-bg-blue',
|
||||||
name: defineMessage({
|
name: commonMessages.planSmallLabel,
|
||||||
id: 'servers.plan.small.name',
|
|
||||||
defaultMessage: 'Small',
|
|
||||||
}),
|
|
||||||
description: defineMessage({
|
description: defineMessage({
|
||||||
id: 'servers.plan.small.description',
|
id: 'servers.plan.small.description',
|
||||||
defaultMessage: 'Perfect for 1–5 friends with a few light mods.',
|
defaultMessage: 'Perfect for 1–5 friends with a few light mods.',
|
||||||
@@ -51,10 +55,7 @@ const plans: Record<
|
|||||||
buttonColor: 'green',
|
buttonColor: 'green',
|
||||||
accentText: 'text-green',
|
accentText: 'text-green',
|
||||||
accentBg: 'bg-bg-green',
|
accentBg: 'bg-bg-green',
|
||||||
name: defineMessage({
|
name: commonMessages.planMediumLabel,
|
||||||
id: 'servers.plan.medium.name',
|
|
||||||
defaultMessage: 'Medium',
|
|
||||||
}),
|
|
||||||
description: defineMessage({
|
description: defineMessage({
|
||||||
id: 'servers.plan.medium.description',
|
id: 'servers.plan.medium.description',
|
||||||
defaultMessage: 'Great for 6–15 players and multiple mods.',
|
defaultMessage: 'Great for 6–15 players and multiple mods.',
|
||||||
@@ -65,10 +66,7 @@ const plans: Record<
|
|||||||
buttonColor: 'purple',
|
buttonColor: 'purple',
|
||||||
accentText: 'text-purple',
|
accentText: 'text-purple',
|
||||||
accentBg: 'bg-bg-purple',
|
accentBg: 'bg-bg-purple',
|
||||||
name: defineMessage({
|
name: commonMessages.planLargeLabel,
|
||||||
id: 'servers.plan.large.name',
|
|
||||||
defaultMessage: 'Large',
|
|
||||||
}),
|
|
||||||
description: defineMessage({
|
description: defineMessage({
|
||||||
id: 'servers.plan.large.description',
|
id: 'servers.plan.large.description',
|
||||||
defaultMessage: 'Ideal for 15–25 players, modpacks, or heavy modding.',
|
defaultMessage: 'Ideal for 15–25 players, modpacks, or heavy modding.',
|
||||||
|
|||||||
@@ -2855,20 +2855,209 @@
|
|||||||
"servers.plan.large.description": {
|
"servers.plan.large.description": {
|
||||||
"message": "Ideal for 15–25 players, modpacks, or heavy modding."
|
"message": "Ideal for 15–25 players, modpacks, or heavy modding."
|
||||||
},
|
},
|
||||||
"servers.plan.large.name": {
|
|
||||||
"message": "Large"
|
|
||||||
},
|
|
||||||
"servers.plan.medium.description": {
|
"servers.plan.medium.description": {
|
||||||
"message": "Great for 6–15 players and multiple mods."
|
"message": "Great for 6–15 players and multiple mods."
|
||||||
},
|
},
|
||||||
"servers.plan.medium.name": {
|
|
||||||
"message": "Medium"
|
|
||||||
},
|
|
||||||
"servers.plan.small.description": {
|
"servers.plan.small.description": {
|
||||||
"message": "Perfect for 1–5 friends with a few light mods."
|
"message": "Perfect for 1–5 friends with a few light mods."
|
||||||
},
|
},
|
||||||
"servers.plan.small.name": {
|
"settings.account.button.complete-setup": {
|
||||||
"message": "Small"
|
"message": "Complete setup"
|
||||||
|
},
|
||||||
|
"settings.account.data-export.action.download": {
|
||||||
|
"message": "Download export"
|
||||||
|
},
|
||||||
|
"settings.account.data-export.action.generate": {
|
||||||
|
"message": "Generate export"
|
||||||
|
},
|
||||||
|
"settings.account.data-export.action.generating": {
|
||||||
|
"message": "Generating export..."
|
||||||
|
},
|
||||||
|
"settings.account.data-export.description": {
|
||||||
|
"message": "Request a copy of all your personal data you have uploaded to Modrinth. This may take several minutes to complete."
|
||||||
|
},
|
||||||
|
"settings.account.data-export.title": {
|
||||||
|
"message": "Data export"
|
||||||
|
},
|
||||||
|
"settings.account.delete.confirm.description": {
|
||||||
|
"message": "This will **immediately delete all of your user data and follows**. This will not delete your projects. Deleting your account cannot be reversed.<br><br>If you need help with your account, get support on the [Modrinth Discord](https://discord.modrinth.com)."
|
||||||
|
},
|
||||||
|
"settings.account.delete.confirm.proceed": {
|
||||||
|
"message": "Delete this account"
|
||||||
|
},
|
||||||
|
"settings.account.delete.confirm.title": {
|
||||||
|
"message": "Are you sure you want to delete your account?"
|
||||||
|
},
|
||||||
|
"settings.account.delete.section.action": {
|
||||||
|
"message": "Delete account"
|
||||||
|
},
|
||||||
|
"settings.account.delete.section.description": {
|
||||||
|
"message": "Once you delete your account, there is no going back. Deleting your account will remove all attached data, excluding projects, from our servers."
|
||||||
|
},
|
||||||
|
"settings.account.delete.section.title": {
|
||||||
|
"message": "Delete account"
|
||||||
|
},
|
||||||
|
"settings.account.email.action.save": {
|
||||||
|
"message": "Save email"
|
||||||
|
},
|
||||||
|
"settings.account.email.field.label": {
|
||||||
|
"message": "Email address"
|
||||||
|
},
|
||||||
|
"settings.account.email.field.placeholder": {
|
||||||
|
"message": "Enter your email address..."
|
||||||
|
},
|
||||||
|
"settings.account.email.modal.header.add": {
|
||||||
|
"message": "Add email"
|
||||||
|
},
|
||||||
|
"settings.account.email.modal.header.change": {
|
||||||
|
"message": "Change email"
|
||||||
|
},
|
||||||
|
"settings.account.email.modal.notice": {
|
||||||
|
"message": "Your account information is not displayed publicly."
|
||||||
|
},
|
||||||
|
"settings.account.password.action.remove": {
|
||||||
|
"message": "Remove password"
|
||||||
|
},
|
||||||
|
"settings.account.password.action.save": {
|
||||||
|
"message": "Save password"
|
||||||
|
},
|
||||||
|
"settings.account.password.error.mismatch": {
|
||||||
|
"message": "Input passwords do not match!"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.confirm-current.description": {
|
||||||
|
"message": "Please enter your password to proceed."
|
||||||
|
},
|
||||||
|
"settings.account.password.field.confirm-current.label": {
|
||||||
|
"message": "Confirm password"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.confirm-current.placeholder": {
|
||||||
|
"message": "Confirm password"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.confirm-new.label": {
|
||||||
|
"message": "Confirm new password"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.confirm-new.placeholder": {
|
||||||
|
"message": "Confirm new password"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.new.label": {
|
||||||
|
"message": "New password"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.new.placeholder": {
|
||||||
|
"message": "New password"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.old.label": {
|
||||||
|
"message": "Old password"
|
||||||
|
},
|
||||||
|
"settings.account.password.field.old.placeholder": {
|
||||||
|
"message": "Old password"
|
||||||
|
},
|
||||||
|
"settings.account.password.modal.header.add": {
|
||||||
|
"message": "Add password"
|
||||||
|
},
|
||||||
|
"settings.account.password.modal.header.change": {
|
||||||
|
"message": "Change password"
|
||||||
|
},
|
||||||
|
"settings.account.password.modal.header.remove": {
|
||||||
|
"message": "Remove password"
|
||||||
|
},
|
||||||
|
"settings.account.providers.action.add": {
|
||||||
|
"message": "Add"
|
||||||
|
},
|
||||||
|
"settings.account.providers.modal.header": {
|
||||||
|
"message": "Authentication providers"
|
||||||
|
},
|
||||||
|
"settings.account.providers.table.actions": {
|
||||||
|
"message": "Actions"
|
||||||
|
},
|
||||||
|
"settings.account.providers.table.provider": {
|
||||||
|
"message": "Provider"
|
||||||
|
},
|
||||||
|
"settings.account.security.email.action.add": {
|
||||||
|
"message": "Add email"
|
||||||
|
},
|
||||||
|
"settings.account.security.email.action.change": {
|
||||||
|
"message": "Change email"
|
||||||
|
},
|
||||||
|
"settings.account.security.email.description": {
|
||||||
|
"message": "Changes the email associated with your account."
|
||||||
|
},
|
||||||
|
"settings.account.security.email.title": {
|
||||||
|
"message": "Email"
|
||||||
|
},
|
||||||
|
"settings.account.security.password.action.add": {
|
||||||
|
"message": "Add password"
|
||||||
|
},
|
||||||
|
"settings.account.security.password.action.change": {
|
||||||
|
"message": "Change password"
|
||||||
|
},
|
||||||
|
"settings.account.security.password.description.change": {
|
||||||
|
"message": "Change the password used to login to your account."
|
||||||
|
},
|
||||||
|
"settings.account.security.password.description.change-or-remove": {
|
||||||
|
"message": "Change or remove the password used to login to your account."
|
||||||
|
},
|
||||||
|
"settings.account.security.password.description.set": {
|
||||||
|
"message": "Set a permanent password to login to your account."
|
||||||
|
},
|
||||||
|
"settings.account.security.password.title": {
|
||||||
|
"message": "Password"
|
||||||
|
},
|
||||||
|
"settings.account.security.providers.action.manage": {
|
||||||
|
"message": "Manage providers"
|
||||||
|
},
|
||||||
|
"settings.account.security.providers.description": {
|
||||||
|
"message": "Add or remove sign-on methods from your account, including GitHub, GitLab, Microsoft, Discord, Steam, and Google."
|
||||||
|
},
|
||||||
|
"settings.account.security.providers.title": {
|
||||||
|
"message": "Manage authentication providers"
|
||||||
|
},
|
||||||
|
"settings.account.security.title": {
|
||||||
|
"message": "Account security"
|
||||||
|
},
|
||||||
|
"settings.account.security.two-factor.action.remove": {
|
||||||
|
"message": "Remove 2FA"
|
||||||
|
},
|
||||||
|
"settings.account.security.two-factor.action.setup": {
|
||||||
|
"message": "Setup 2FA"
|
||||||
|
},
|
||||||
|
"settings.account.security.two-factor.description": {
|
||||||
|
"message": "Add an additional layer of security to your account during login."
|
||||||
|
},
|
||||||
|
"settings.account.security.two-factor.title": {
|
||||||
|
"message": "Two-factor authentication"
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.backup.intro": {
|
||||||
|
"message": "Download and save these back-up codes in a safe place. You can use these in-place of a 2FA code if you ever lose access to your device! You should protect these codes like your password."
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.backup.single-use": {
|
||||||
|
"message": "Backup codes can only be used once."
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.error.incorrect-code": {
|
||||||
|
"message": "The code entered is incorrect!"
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.field.code.description": {
|
||||||
|
"message": "Please enter a two-factor code to proceed."
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.field.code.label": {
|
||||||
|
"message": "Enter two-factor code"
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.field.code.placeholder": {
|
||||||
|
"message": "Enter code..."
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.setup.intro": {
|
||||||
|
"message": "Two-factor authentication keeps your account secure by requiring access to a second device in order to sign in."
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.setup.manual-secret": {
|
||||||
|
"message": "If the QR code does not scan, you can manually enter the secret:"
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.setup.scan": {
|
||||||
|
"message": "Scan the QR code with <authy-link>Authy</authy-link>, <microsoft-authenticator-link>Microsoft Authenticator</microsoft-authenticator-link>, or any other 2FA app to begin."
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.verify.description": {
|
||||||
|
"message": "Enter the one-time code from authenticator to verify access."
|
||||||
|
},
|
||||||
|
"settings.account.two-factor.verify.label": {
|
||||||
|
"message": "Verify code"
|
||||||
},
|
},
|
||||||
"settings.applications.about": {
|
"settings.applications.about": {
|
||||||
"message": "About"
|
"message": "About"
|
||||||
@@ -2951,12 +3140,12 @@
|
|||||||
"settings.applications.field.url.placeholder": {
|
"settings.applications.field.url.placeholder": {
|
||||||
"message": "https://example.com"
|
"message": "https://example.com"
|
||||||
},
|
},
|
||||||
|
"settings.applications.head-title": {
|
||||||
|
"message": "Applications"
|
||||||
|
},
|
||||||
"settings.applications.modal.header": {
|
"settings.applications.modal.header": {
|
||||||
"message": "Application information"
|
"message": "Application information"
|
||||||
},
|
},
|
||||||
"settings.applications.notification.error.title": {
|
|
||||||
"message": "An error occurred"
|
|
||||||
},
|
|
||||||
"settings.applications.notification.icon-updated.description": {
|
"settings.applications.notification.icon-updated.description": {
|
||||||
"message": "Your application icon has been updated."
|
"message": "Your application icon has been updated."
|
||||||
},
|
},
|
||||||
@@ -2966,6 +3155,90 @@
|
|||||||
"settings.applications.secret.disclaimer": {
|
"settings.applications.secret.disclaimer": {
|
||||||
"message": "Save your secret now, it will be hidden after you leave this page!"
|
"message": "Save your secret now, it will be hidden after you leave this page!"
|
||||||
},
|
},
|
||||||
|
"settings.authorizations.about-this-app": {
|
||||||
|
"message": "About this app"
|
||||||
|
},
|
||||||
|
"settings.authorizations.by": {
|
||||||
|
"message": "by"
|
||||||
|
},
|
||||||
|
"settings.authorizations.description": {
|
||||||
|
"message": "When you authorize an application with your Modrinth account, you grant it access to your account. You can manage and review access to your account here at any time."
|
||||||
|
},
|
||||||
|
"settings.authorizations.empty-state": {
|
||||||
|
"message": "We currently can't display your authorized apps, we're working to fix this. Please visit this page at a later date!"
|
||||||
|
},
|
||||||
|
"settings.authorizations.head-title": {
|
||||||
|
"message": "Authorizations"
|
||||||
|
},
|
||||||
|
"settings.authorizations.revoke.action": {
|
||||||
|
"message": "Revoke"
|
||||||
|
},
|
||||||
|
"settings.authorizations.revoke.confirm.description": {
|
||||||
|
"message": "This will revoke the application's access to your account. You can always re-authorize it later."
|
||||||
|
},
|
||||||
|
"settings.authorizations.revoke.confirm.title": {
|
||||||
|
"message": "Are you sure you want to revoke this application?"
|
||||||
|
},
|
||||||
|
"settings.billing.charges.description": {
|
||||||
|
"message": "All of your past charges to your Modrinth account will be listed here:"
|
||||||
|
},
|
||||||
|
"settings.billing.charges.product.medal-trial": {
|
||||||
|
"message": "Medal Server Trial"
|
||||||
|
},
|
||||||
|
"settings.billing.charges.product.midas": {
|
||||||
|
"message": "Modrinth Plus"
|
||||||
|
},
|
||||||
|
"settings.billing.charges.product.pyro": {
|
||||||
|
"message": "Modrinth Hosting"
|
||||||
|
},
|
||||||
|
"settings.billing.expires": {
|
||||||
|
"message": "Expires {date}"
|
||||||
|
},
|
||||||
|
"settings.billing.interval.month": {
|
||||||
|
"message": "month"
|
||||||
|
},
|
||||||
|
"settings.billing.interval.monthly": {
|
||||||
|
"message": "monthly"
|
||||||
|
},
|
||||||
|
"settings.billing.interval.year": {
|
||||||
|
"message": "year"
|
||||||
|
},
|
||||||
|
"settings.billing.interval.yearly": {
|
||||||
|
"message": "yearly"
|
||||||
|
},
|
||||||
|
"settings.billing.midas.benefits.ad-free": {
|
||||||
|
"message": "Ad-free browsing on modrinth.com and Modrinth App"
|
||||||
|
},
|
||||||
|
"settings.billing.midas.benefits.badge": {
|
||||||
|
"message": "Modrinth+ badge on your profile"
|
||||||
|
},
|
||||||
|
"settings.billing.midas.benefits.support": {
|
||||||
|
"message": "Support Modrinth and creators directly"
|
||||||
|
},
|
||||||
|
"settings.billing.midas.benefits.title": {
|
||||||
|
"message": "Benefits"
|
||||||
|
},
|
||||||
|
"settings.billing.midas.save-per-year": {
|
||||||
|
"message": "Save {amount}/year by switching to yearly billing!"
|
||||||
|
},
|
||||||
|
"settings.billing.midas.status.cancelled.line1": {
|
||||||
|
"message": "You've cancelled your subscription."
|
||||||
|
},
|
||||||
|
"settings.billing.midas.status.cancelled.line2": {
|
||||||
|
"message": "You will retain your perks until the end of the current billing cycle."
|
||||||
|
},
|
||||||
|
"settings.billing.midas.status.failed": {
|
||||||
|
"message": "Your subscription payment failed. Please update your payment method."
|
||||||
|
},
|
||||||
|
"settings.billing.midas.status.open": {
|
||||||
|
"message": "You're currently subscribed to:"
|
||||||
|
},
|
||||||
|
"settings.billing.midas.status.processing": {
|
||||||
|
"message": "Your payment is being processed. Perks will activate once payment is complete."
|
||||||
|
},
|
||||||
|
"settings.billing.midas.upsell": {
|
||||||
|
"message": "Become a subscriber to Modrinth Plus!"
|
||||||
|
},
|
||||||
"settings.billing.modal.cancel.action": {
|
"settings.billing.modal.cancel.action": {
|
||||||
"message": "Cancel subscription"
|
"message": "Cancel subscription"
|
||||||
},
|
},
|
||||||
@@ -2984,6 +3257,12 @@
|
|||||||
"settings.billing.modal.delete.title": {
|
"settings.billing.modal.delete.title": {
|
||||||
"message": "Are you sure you want to remove this payment method?"
|
"message": "Are you sure you want to remove this payment method?"
|
||||||
},
|
},
|
||||||
|
"settings.billing.next": {
|
||||||
|
"message": "Next:"
|
||||||
|
},
|
||||||
|
"settings.billing.or-yearly-save": {
|
||||||
|
"message": "Or {price} / year (save {percent}%)!"
|
||||||
|
},
|
||||||
"settings.billing.payment_method.action.add": {
|
"settings.billing.payment_method.action.add": {
|
||||||
"message": "Add payment method"
|
"message": "Add payment method"
|
||||||
},
|
},
|
||||||
@@ -3005,18 +3284,99 @@
|
|||||||
"settings.billing.payment_method.title": {
|
"settings.billing.payment_method.title": {
|
||||||
"message": "Payment methods"
|
"message": "Payment methods"
|
||||||
},
|
},
|
||||||
|
"settings.billing.plan.title": {
|
||||||
|
"message": "{size} Plan"
|
||||||
|
},
|
||||||
|
"settings.billing.price.per-interval": {
|
||||||
|
"message": "{price} / {interval}"
|
||||||
|
},
|
||||||
|
"settings.billing.price.slash-interval": {
|
||||||
|
"message": "/{interval}"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.cpu": {
|
||||||
|
"message": "{shared} Shared CPUs (Bursts up to {bursts} CPUs)"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.linked-server.not-found": {
|
||||||
|
"message": "A linked server couldn't be found for this subscription. There are a few possible explanations for this. If you just purchased your server, this is normal. It could take up to an hour for your server to be provisioned. Otherwise, if you purchased this server a while ago, it has likely since been suspended. If this is not what you were expecting, please contact Modrinth Support with the following information:"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.linked-server.server-id": {
|
||||||
|
"message": "Server ID: {id}"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.linked-server.stripe-id": {
|
||||||
|
"message": "Stripe ID: {id}"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.ram": {
|
||||||
|
"message": "{gb} GB RAM"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.resubscribe.error.text": {
|
||||||
|
"message": "An error occurred while resubscribing to your Modrinth server."
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.resubscribe.error.title": {
|
||||||
|
"message": "Error resubscribing"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.resubscribe.request-submitted.text": {
|
||||||
|
"message": "If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made."
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.resubscribe.request-submitted.title": {
|
||||||
|
"message": "Resubscription request submitted"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.resubscribe.success.text": {
|
||||||
|
"message": "Server subscription resubscribed successfully"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.status.failed": {
|
||||||
|
"message": "Your subscription payment failed. Please update your payment method, then resubscribe."
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.status.processing": {
|
||||||
|
"message": "Your payment is being processed. Your server will activate once payment is complete."
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.storage": {
|
||||||
|
"message": "{gb} GB SSD"
|
||||||
|
},
|
||||||
|
"settings.billing.pyro.swap": {
|
||||||
|
"message": "{gb} GB Swap"
|
||||||
|
},
|
||||||
"settings.billing.pyro_subscription.description": {
|
"settings.billing.pyro_subscription.description": {
|
||||||
"message": "Manage your Modrinth Server subscriptions."
|
"message": "Manage your Modrinth Server subscriptions."
|
||||||
},
|
},
|
||||||
"settings.billing.pyro_subscription.title": {
|
"settings.billing.pyro_subscription.title": {
|
||||||
"message": "Modrinth Server Subscriptions"
|
"message": "Modrinth Server Subscriptions"
|
||||||
},
|
},
|
||||||
|
"settings.billing.renews": {
|
||||||
|
"message": "Renews {date}"
|
||||||
|
},
|
||||||
|
"settings.billing.resubscribe": {
|
||||||
|
"message": "Resubscribe"
|
||||||
|
},
|
||||||
|
"settings.billing.since": {
|
||||||
|
"message": "Since {date}"
|
||||||
|
},
|
||||||
|
"settings.billing.subscribe": {
|
||||||
|
"message": "Subscribe"
|
||||||
|
},
|
||||||
"settings.billing.subscription.description": {
|
"settings.billing.subscription.description": {
|
||||||
"message": "Manage your Modrinth subscriptions."
|
"message": "Manage your Modrinth subscriptions."
|
||||||
},
|
},
|
||||||
"settings.billing.subscription.title": {
|
"settings.billing.subscription.title": {
|
||||||
"message": "Subscriptions"
|
"message": "Subscriptions"
|
||||||
},
|
},
|
||||||
|
"settings.billing.switch.switching-to-interval": {
|
||||||
|
"message": "Switching to {interval}"
|
||||||
|
},
|
||||||
|
"settings.billing.switch.to-interval": {
|
||||||
|
"message": "Switch to {interval}"
|
||||||
|
},
|
||||||
|
"settings.billing.switch.tooltip.monthly-additional-per-year": {
|
||||||
|
"message": "Monthly billing will cost you an additional {amount} per year"
|
||||||
|
},
|
||||||
|
"settings.billing.switches-to-billing-on": {
|
||||||
|
"message": "Switches to {interval} billing on {date}"
|
||||||
|
},
|
||||||
|
"settings.billing.update-method": {
|
||||||
|
"message": "Update method"
|
||||||
|
},
|
||||||
|
"settings.billing.upgrade": {
|
||||||
|
"message": "Upgrade"
|
||||||
|
},
|
||||||
"settings.display.banner.developer-mode.button": {
|
"settings.display.banner.developer-mode.button": {
|
||||||
"message": "Deactivate developer mode"
|
"message": "Deactivate developer mode"
|
||||||
},
|
},
|
||||||
@@ -3029,6 +3389,12 @@
|
|||||||
"settings.display.flags.title": {
|
"settings.display.flags.title": {
|
||||||
"message": "Toggle features"
|
"message": "Toggle features"
|
||||||
},
|
},
|
||||||
|
"settings.display.notification.developer-mode-deactivated.text": {
|
||||||
|
"message": "Developer mode has been disabled"
|
||||||
|
},
|
||||||
|
"settings.display.notification.developer-mode-deactivated.title": {
|
||||||
|
"message": "Developer mode deactivated"
|
||||||
|
},
|
||||||
"settings.display.project-list-layouts.datapack": {
|
"settings.display.project-list-layouts.datapack": {
|
||||||
"message": "Data Packs page"
|
"message": "Data Packs page"
|
||||||
},
|
},
|
||||||
@@ -3038,6 +3404,15 @@
|
|||||||
"settings.display.project-list-layouts.mod": {
|
"settings.display.project-list-layouts.mod": {
|
||||||
"message": "Mods page"
|
"message": "Mods page"
|
||||||
},
|
},
|
||||||
|
"settings.display.project-list-layouts.mode.gallery": {
|
||||||
|
"message": "Gallery"
|
||||||
|
},
|
||||||
|
"settings.display.project-list-layouts.mode.grid": {
|
||||||
|
"message": "Grid"
|
||||||
|
},
|
||||||
|
"settings.display.project-list-layouts.mode.rows": {
|
||||||
|
"message": "Rows"
|
||||||
|
},
|
||||||
"settings.display.project-list-layouts.modpack": {
|
"settings.display.project-list-layouts.modpack": {
|
||||||
"message": "Modpacks page"
|
"message": "Modpacks page"
|
||||||
},
|
},
|
||||||
@@ -3098,6 +3473,9 @@
|
|||||||
"settings.display.theme.title": {
|
"settings.display.theme.title": {
|
||||||
"message": "Color theme"
|
"message": "Color theme"
|
||||||
},
|
},
|
||||||
|
"settings.head-title": {
|
||||||
|
"message": "Display settings"
|
||||||
|
},
|
||||||
"settings.pats.action.create": {
|
"settings.pats.action.create": {
|
||||||
"message": "Create a PAT"
|
"message": "Create a PAT"
|
||||||
},
|
},
|
||||||
@@ -3158,6 +3536,9 @@
|
|||||||
"settings.profile.description": {
|
"settings.profile.description": {
|
||||||
"message": "Your profile information is publicly viewable on Modrinth and through the <docs-link>Modrinth API</docs-link>."
|
"message": "Your profile information is publicly viewable on Modrinth and through the <docs-link>Modrinth API</docs-link>."
|
||||||
},
|
},
|
||||||
|
"settings.profile.head-title": {
|
||||||
|
"message": "Profile settings"
|
||||||
|
},
|
||||||
"settings.profile.profile-info": {
|
"settings.profile.profile-info": {
|
||||||
"message": "Profile information"
|
"message": "Profile information"
|
||||||
},
|
},
|
||||||
@@ -3188,6 +3569,15 @@
|
|||||||
"settings.sessions.unknown-platform": {
|
"settings.sessions.unknown-platform": {
|
||||||
"message": "Unknown platform"
|
"message": "Unknown platform"
|
||||||
},
|
},
|
||||||
|
"settings.sidebar.label.account": {
|
||||||
|
"message": "Account"
|
||||||
|
},
|
||||||
|
"settings.sidebar.label.developer": {
|
||||||
|
"message": "Developer"
|
||||||
|
},
|
||||||
|
"settings.sidebar.label.display": {
|
||||||
|
"message": "Display"
|
||||||
|
},
|
||||||
"ui.latest-news-row.latest-news": {
|
"ui.latest-news-row.latest-news": {
|
||||||
"message": "Latest news from Modrinth"
|
"message": "Latest news from Modrinth"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<NavStack
|
<NavStack
|
||||||
:items="
|
:items="
|
||||||
[
|
[
|
||||||
{ type: 'heading', label: 'Display' },
|
{ type: 'heading', label: formatMessage(messages.display) },
|
||||||
{
|
{
|
||||||
link: '/settings',
|
link: '/settings',
|
||||||
label: formatMessage(commonSettingsMessages.appearance),
|
label: formatMessage(commonSettingsMessages.appearance),
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
icon: LanguagesIcon,
|
icon: LanguagesIcon,
|
||||||
badge: `${formatMessage(commonMessages.beta)}`,
|
badge: `${formatMessage(commonMessages.beta)}`,
|
||||||
},
|
},
|
||||||
auth.user ? { type: 'heading', label: 'Account' } : null,
|
auth.user ? { type: 'heading', label: formatMessage(messages.account) } : null,
|
||||||
auth.user
|
auth.user
|
||||||
? {
|
? {
|
||||||
link: '/settings/profile',
|
link: '/settings/profile',
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
icon: CardIcon,
|
icon: CardIcon,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
auth.user ? { type: 'heading', label: 'Developer' } : null,
|
auth.user ? { type: 'heading', label: formatMessage(messages.developer) } : null,
|
||||||
auth.user
|
auth.user
|
||||||
? {
|
? {
|
||||||
link: '/settings/pats',
|
link: '/settings/pats',
|
||||||
@@ -93,12 +93,27 @@ import {
|
|||||||
ShieldIcon,
|
ShieldIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { commonMessages, commonSettingsMessages, useVIntl } from '@modrinth/ui'
|
import { commonMessages, commonSettingsMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
import NavStack from '~/components/ui/NavStack.vue'
|
import NavStack from '~/components/ui/NavStack.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
display: {
|
||||||
|
id: 'settings.sidebar.label.display',
|
||||||
|
defaultMessage: 'Display',
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
id: 'settings.sidebar.label.account',
|
||||||
|
defaultMessage: 'Account',
|
||||||
|
},
|
||||||
|
developer: {
|
||||||
|
id: 'settings.sidebar.label.developer',
|
||||||
|
defaultMessage: 'Developer',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const route = useNativeRoute()
|
const route = useNativeRoute()
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
|
|
||||||
|
|||||||
@@ -2,29 +2,34 @@
|
|||||||
<div>
|
<div>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
ref="modal_confirm"
|
ref="modal_confirm"
|
||||||
title="Are you sure you want to delete your account?"
|
:title="formatMessage(messages.deleteAccountConfirmTitle)"
|
||||||
description="This will **immediately delete all of your user data and follows**. This will not delete your projects. Deleting your account cannot be reversed.<br><br>If you need help with your account, get support on the [Modrinth Discord](https://discord.modrinth.com)."
|
:description="formatMessage(messages.deleteAccountConfirmDescription)"
|
||||||
proceed-label="Delete this account"
|
:proceed-label="formatMessage(messages.deleteAccountConfirmProceed)"
|
||||||
:confirmation-text="auth.user.username"
|
:confirmation-text="auth.user.username"
|
||||||
:has-to-type="true"
|
:has-to-type="true"
|
||||||
@proceed="deleteAccount"
|
@proceed="deleteAccount"
|
||||||
/>
|
/>
|
||||||
<Modal ref="changeEmailModal" :header="`${auth.user.email ? 'Change' : 'Add'} email`">
|
<Modal
|
||||||
|
ref="changeEmailModal"
|
||||||
|
:header="`${auth.user.email ? formatMessage(messages.changeEmailHeaderChange) : formatMessage(messages.changeEmailHeaderAdd)}`"
|
||||||
|
>
|
||||||
<div class="universal-modal">
|
<div class="universal-modal">
|
||||||
<p>Your account information is not displayed publicly.</p>
|
<p>{{ formatMessage(messages.emailNotPublicNotice) }}</p>
|
||||||
<label for="email-input"><span class="label__title">Email address</span> </label>
|
<label for="email-input">
|
||||||
|
<span class="label__title">{{ formatMessage(messages.emailAddressLabel) }}</span>
|
||||||
|
</label>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
id="email-input"
|
id="email-input"
|
||||||
v-model="email"
|
v-model="email"
|
||||||
:maxlength="2048"
|
:maxlength="2048"
|
||||||
type="email"
|
type="email"
|
||||||
:placeholder="`Enter your email address...`"
|
:placeholder="formatMessage(messages.emailAddressPlaceholder)"
|
||||||
@keyup.enter="saveEmail()"
|
@keyup.enter="saveEmail()"
|
||||||
/>
|
/>
|
||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
<button class="iconified-button" @click="$refs.changeEmailModal.hide()">
|
<button class="iconified-button" @click="$refs.changeEmailModal.hide()">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
{{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -33,7 +38,7 @@
|
|||||||
@click="saveEmail()"
|
@click="saveEmail()"
|
||||||
>
|
>
|
||||||
<SaveIcon />
|
<SaveIcon />
|
||||||
Save email
|
{{ formatMessage(messages.saveEmailButton) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,22 +46,28 @@
|
|||||||
<Modal
|
<Modal
|
||||||
ref="managePasswordModal"
|
ref="managePasswordModal"
|
||||||
:header="`${
|
:header="`${
|
||||||
removePasswordMode ? 'Remove' : auth.user.has_password ? 'Change' : 'Add'
|
removePasswordMode
|
||||||
} password`"
|
? formatMessage(messages.passwordHeaderRemove)
|
||||||
|
: auth.user.has_password
|
||||||
|
? formatMessage(messages.passwordHeaderChange)
|
||||||
|
: formatMessage(messages.passwordHeaderAdd)
|
||||||
|
}`"
|
||||||
>
|
>
|
||||||
<div class="universal-modal">
|
<div class="universal-modal">
|
||||||
<ul
|
<ul
|
||||||
v-if="newPassword !== confirmNewPassword && confirmNewPassword.length > 0"
|
v-if="newPassword !== confirmNewPassword && confirmNewPassword.length > 0"
|
||||||
class="known-errors"
|
class="known-errors"
|
||||||
>
|
>
|
||||||
<li>Input passwords do not match!</li>
|
<li>{{ formatMessage(messages.passwordsDoNotMatchError) }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<label v-if="removePasswordMode" for="old-password">
|
<label v-if="removePasswordMode" for="old-password">
|
||||||
<span class="label__title">Confirm password</span>
|
<span class="label__title">{{ formatMessage(messages.confirmPasswordLabel) }}</span>
|
||||||
<span class="label__description">Please enter your password to proceed.</span>
|
<span class="label__description">{{
|
||||||
|
formatMessage(messages.confirmPasswordDescription)
|
||||||
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
<label v-else-if="auth.user.has_password" for="old-password">
|
<label v-else-if="auth.user.has_password" for="old-password">
|
||||||
<span class="label__title">Old password</span>
|
<span class="label__title">{{ formatMessage(messages.oldPasswordLabel) }}</span>
|
||||||
</label>
|
</label>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
v-if="auth.user.has_password"
|
v-if="auth.user.has_password"
|
||||||
@@ -65,35 +76,41 @@
|
|||||||
:maxlength="2048"
|
:maxlength="2048"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
:placeholder="`${removePasswordMode ? 'Confirm' : 'Old'} password`"
|
:placeholder="
|
||||||
|
removePasswordMode
|
||||||
|
? formatMessage(messages.confirmPasswordPlaceholder)
|
||||||
|
: formatMessage(messages.oldPasswordPlaceholder)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<template v-if="!removePasswordMode">
|
<template v-if="!removePasswordMode">
|
||||||
<label for="new-password"><span class="label__title">New password</span></label>
|
<label for="new-password"
|
||||||
|
><span class="label__title">{{ formatMessage(messages.newPasswordLabel) }}</span></label
|
||||||
|
>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
id="new-password"
|
id="new-password"
|
||||||
v-model="newPassword"
|
v-model="newPassword"
|
||||||
:maxlength="2048"
|
:maxlength="2048"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
placeholder="New password"
|
:placeholder="formatMessage(messages.newPasswordPlaceholder)"
|
||||||
/>
|
/>
|
||||||
<label for="confirm-new-password"
|
<label for="confirm-new-password">
|
||||||
><span class="label__title">Confirm new password</span></label
|
<span class="label__title">{{ formatMessage(messages.confirmNewPasswordLabel) }}</span>
|
||||||
>
|
</label>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
id="confirm-new-password"
|
id="confirm-new-password"
|
||||||
v-model="confirmNewPassword"
|
v-model="confirmNewPassword"
|
||||||
:maxlength="2048"
|
:maxlength="2048"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
placeholder="Confirm new password"
|
:placeholder="formatMessage(messages.confirmNewPasswordPlaceholder)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<p></p>
|
<p></p>
|
||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
<button class="iconified-button" @click="$refs.managePasswordModal.hide()">
|
<button class="iconified-button" @click="$refs.managePasswordModal.hide()">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
{{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</button>
|
</button>
|
||||||
<template v-if="removePasswordMode">
|
<template v-if="removePasswordMode">
|
||||||
<button
|
<button
|
||||||
@@ -103,7 +120,7 @@
|
|||||||
@click="savePassword"
|
@click="savePassword"
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Remove password
|
{{ formatMessage(messages.removePasswordButton) }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -114,7 +131,7 @@
|
|||||||
@click="removePasswordMode = true"
|
@click="removePasswordMode = true"
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Remove password
|
{{ formatMessage(messages.removePasswordButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -127,7 +144,7 @@
|
|||||||
@click="savePassword"
|
@click="savePassword"
|
||||||
>
|
>
|
||||||
<SaveIcon />
|
<SaveIcon />
|
||||||
Save password
|
{{ formatMessage(messages.savePasswordButton) }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,45 +152,57 @@
|
|||||||
</Modal>
|
</Modal>
|
||||||
<Modal
|
<Modal
|
||||||
ref="manageTwoFactorModal"
|
ref="manageTwoFactorModal"
|
||||||
:header="`${
|
:header="`${auth.user.has_totp && twoFactorStep === 0 ? formatMessage(messages.twoFactorRemoveButton) : formatMessage(messages.twoFactorSetupButton)}`"
|
||||||
auth.user.has_totp && twoFactorStep === 0 ? 'Remove' : 'Setup'
|
|
||||||
} two-factor authentication`"
|
|
||||||
>
|
>
|
||||||
<div class="universal-modal">
|
<div class="universal-modal">
|
||||||
<template v-if="auth.user.has_totp && twoFactorStep === 0">
|
<template v-if="auth.user.has_totp && twoFactorStep === 0">
|
||||||
<label for="two-factor-code">
|
<label for="two-factor-code">
|
||||||
<span class="label__title">Enter two-factor code</span>
|
<span class="label__title">{{ formatMessage(messages.twoFactorEnterCodeLabel) }}</span>
|
||||||
<span class="label__description">Please enter a two-factor code to proceed.</span>
|
<span class="label__description">{{
|
||||||
|
formatMessage(messages.twoFactorEnterCodeDescription)
|
||||||
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
id="two-factor-code"
|
id="two-factor-code"
|
||||||
v-model="twoFactorCode"
|
v-model="twoFactorCode"
|
||||||
:maxlength="11"
|
:maxlength="11"
|
||||||
placeholder="Enter code..."
|
:placeholder="formatMessage(messages.twoFactorCodePlaceholder)"
|
||||||
@keyup.enter="removeTwoFactor()"
|
@keyup.enter="removeTwoFactor()"
|
||||||
/>
|
/>
|
||||||
<p v-if="twoFactorIncorrect" class="known-errors">The code entered is incorrect!</p>
|
<p v-if="twoFactorIncorrect" class="known-errors">
|
||||||
|
{{ formatMessage(messages.twoFactorIncorrectError) }}
|
||||||
|
</p>
|
||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
<button class="iconified-button" @click="$refs.manageTwoFactorModal.hide()">
|
<button class="iconified-button" @click="$refs.manageTwoFactorModal.hide()">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
{{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button class="iconified-button danger-button" @click="removeTwoFactor">
|
<button class="iconified-button danger-button" @click="removeTwoFactor">
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Remove 2FA
|
{{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-if="twoFactorStep === 0">
|
<template v-if="twoFactorStep === 0">
|
||||||
|
<p>{{ formatMessage(messages.twoFactorSetupIntro) }}</p>
|
||||||
<p>
|
<p>
|
||||||
Two-factor authentication keeps your account secure by requiring access to a second
|
<IntlFormatted :message-id="messages.twoFactorSetupScan">
|
||||||
device in order to sign in.
|
<template #authy-link="{ children }">
|
||||||
<br /><br />
|
<a href="https://authy.com/" target="_blank" rel="noreferrer">
|
||||||
Scan the QR code with <a href="https://authy.com/">Authy</a>,
|
<component :is="() => children" />
|
||||||
<a href="https://www.microsoft.com/en-us/security/mobile-authenticator-app">
|
</a>
|
||||||
Microsoft Authenticator</a
|
</template>
|
||||||
>, or any other 2FA app to begin.
|
<template #microsoft-authenticator-link="{ children }">
|
||||||
|
<a
|
||||||
|
href="https://www.microsoft.com/en-us/security/mobile-authenticator-app"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<component :is="() => children" />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</p>
|
</p>
|
||||||
<qrcode-vue
|
<qrcode-vue
|
||||||
v-if="twoFactorSecret"
|
v-if="twoFactorSecret"
|
||||||
@@ -185,34 +214,34 @@
|
|||||||
level="H"
|
level="H"
|
||||||
/>
|
/>
|
||||||
<p>
|
<p>
|
||||||
If the QR code does not scan, you can manually enter the secret:
|
{{ formatMessage(messages.twoFactorManualSecretPrefix) }}
|
||||||
<strong>{{ twoFactorSecret }}</strong>
|
<strong>{{ twoFactorSecret }}</strong>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="twoFactorStep === 1">
|
<template v-if="twoFactorStep === 1">
|
||||||
<label for="verify-code">
|
<label for="verify-code">
|
||||||
<span class="label__title">Verify code</span>
|
<span class="label__title">{{
|
||||||
<span class="label__description"
|
formatMessage(messages.twoFactorVerifyCodeLabel)
|
||||||
>Enter the one-time code from authenticator to verify access.
|
}}</span>
|
||||||
</span>
|
<span class="label__description">{{
|
||||||
|
formatMessage(messages.twoFactorVerifyCodeDescription)
|
||||||
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
id="verify-code"
|
id="verify-code"
|
||||||
v-model="twoFactorCode"
|
v-model="twoFactorCode"
|
||||||
:maxlength="6"
|
:maxlength="6"
|
||||||
autocomplete="one-time-code"
|
autocomplete="one-time-code"
|
||||||
placeholder="Enter code..."
|
:placeholder="formatMessage(messages.twoFactorCodePlaceholder)"
|
||||||
@keyup.enter="verifyTwoFactorCode()"
|
@keyup.enter="verifyTwoFactorCode()"
|
||||||
/>
|
/>
|
||||||
<p v-if="twoFactorIncorrect" class="known-errors">The code entered is incorrect!</p>
|
<p v-if="twoFactorIncorrect" class="known-errors">
|
||||||
|
{{ formatMessage(messages.twoFactorIncorrectError) }}
|
||||||
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="twoFactorStep === 2">
|
<template v-if="twoFactorStep === 2">
|
||||||
<p>
|
<p>{{ formatMessage(messages.twoFactorBackupCodesIntro) }}</p>
|
||||||
Download and save these back-up codes in a safe place. You can use these in-place of a
|
<p>{{ formatMessage(messages.twoFactorBackupCodesSingleUse) }}</p>
|
||||||
2FA code if you ever lose access to your device! You should protect these codes like
|
|
||||||
your password.
|
|
||||||
</p>
|
|
||||||
<p>Backup codes can only be used once.</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="code in backupCodes" :key="code">{{ code }}</li>
|
<li v-for="code in backupCodes" :key="code">{{ code }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -220,7 +249,7 @@
|
|||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
<button v-if="twoFactorStep === 1" class="iconified-button" @click="twoFactorStep = 0">
|
<button v-if="twoFactorStep === 1" class="iconified-button" @click="twoFactorStep = 0">
|
||||||
<LeftArrowIcon />
|
<LeftArrowIcon />
|
||||||
Back
|
{{ formatMessage(commonMessages.backButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="twoFactorStep !== 2"
|
v-if="twoFactorStep !== 2"
|
||||||
@@ -228,7 +257,7 @@
|
|||||||
@click="$refs.manageTwoFactorModal.hide()"
|
@click="$refs.manageTwoFactorModal.hide()"
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
{{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="twoFactorStep <= 1"
|
v-if="twoFactorStep <= 1"
|
||||||
@@ -236,7 +265,7 @@
|
|||||||
@click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)"
|
@click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)"
|
||||||
>
|
>
|
||||||
<RightArrowIcon />
|
<RightArrowIcon />
|
||||||
Continue
|
{{ formatMessage(commonMessages.continueButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="twoFactorStep === 2"
|
v-if="twoFactorStep === 2"
|
||||||
@@ -244,18 +273,22 @@
|
|||||||
@click="$refs.manageTwoFactorModal.hide()"
|
@click="$refs.manageTwoFactorModal.hide()"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
Complete setup
|
{{ formatMessage(messages.completeSetupButton) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal ref="manageProvidersModal" header="Authentication providers">
|
<Modal ref="manageProvidersModal" :header="formatMessage(messages.manageProvidersModalHeader)">
|
||||||
<div class="universal-modal">
|
<div class="universal-modal">
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<div class="table-head table-row">
|
<div class="table-head table-row">
|
||||||
<div class="table-text table-cell">Provider</div>
|
<div class="table-text table-cell">
|
||||||
<div class="table-text table-cell">Actions</div>
|
{{ formatMessage(messages.providersTableProvider) }}
|
||||||
|
</div>
|
||||||
|
<div class="table-text table-cell">
|
||||||
|
{{ formatMessage(messages.providersTableActions) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="provider in authProviders" :key="provider.id" class="table-row">
|
<div v-for="provider in authProviders" :key="provider.id" class="table-row">
|
||||||
<div class="table-text table-cell">
|
<div class="table-text table-cell">
|
||||||
@@ -267,14 +300,14 @@
|
|||||||
class="btn"
|
class="btn"
|
||||||
@click="handleRemoveAuthProvider(provider.id)"
|
@click="handleRemoveAuthProvider(provider.id)"
|
||||||
>
|
>
|
||||||
<TrashIcon /> Remove
|
<TrashIcon /> {{ formatMessage(commonMessages.removeButton) }}
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
v-else
|
v-else
|
||||||
class="btn"
|
class="btn"
|
||||||
:href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`"
|
:href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`"
|
||||||
>
|
>
|
||||||
<ExternalIcon /> Add
|
<ExternalIcon /> {{ formatMessage(messages.providerAddButton) }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -283,42 +316,46 @@
|
|||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
<button class="iconified-button" @click="$refs.manageProvidersModal.hide()">
|
<button class="iconified-button" @click="$refs.manageProvidersModal.hide()">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Close
|
{{ formatMessage(commonMessages.closeButton) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
<section class="universal-card">
|
<section class="universal-card">
|
||||||
<h2 class="text-2xl">Account security</h2>
|
<h2 class="text-2xl">{{ formatMessage(messages.accountSecurityTitle) }}</h2>
|
||||||
|
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label for="theme-selector">
|
<label for="theme-selector">
|
||||||
<span class="label__title">Email</span>
|
<span class="label__title">{{ formatMessage(messages.emailFieldTitle) }}</span>
|
||||||
<span class="label__description">Changes the email associated with your account.</span>
|
<span class="label__description">{{
|
||||||
|
formatMessage(messages.emailFieldDescription)
|
||||||
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<button class="iconified-button" @click="$refs.changeEmailModal.show()">
|
<button class="iconified-button" @click="$refs.changeEmailModal.show()">
|
||||||
<template v-if="auth.user.email">
|
<template v-if="auth.user.email">
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
Change email
|
{{ formatMessage(messages.changeEmailButton) }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
Add email
|
{{ formatMessage(messages.addEmailButton) }}
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label for="theme-selector">
|
<label for="theme-selector">
|
||||||
<span class="label__title">Password</span>
|
<span class="label__title">{{ formatMessage(messages.passwordFieldTitle) }}</span>
|
||||||
<span v-if="auth.user.has_password" class="label__description">
|
<span v-if="auth.user.has_password" class="label__description">
|
||||||
Change
|
{{
|
||||||
<template v-if="auth.user.auth_providers.length > 0">or remove</template>
|
auth.user.auth_providers.length > 0
|
||||||
the password used to login to your account.
|
? formatMessage(messages.passwordDescriptionChangeOrRemove)
|
||||||
|
: formatMessage(messages.passwordDescriptionChange)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="label__description">
|
<span v-else class="label__description">
|
||||||
Set a permanent password to login to your account.
|
{{ formatMessage(messages.passwordDescriptionSet) }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
@@ -335,70 +372,73 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<KeyIcon />
|
<KeyIcon />
|
||||||
<template v-if="auth.user.has_password"> Change password </template>
|
<template v-if="auth.user.has_password">{{
|
||||||
<template v-else> Add password </template>
|
formatMessage(messages.changePasswordButton)
|
||||||
|
}}</template>
|
||||||
|
<template v-else> {{ formatMessage(messages.addPasswordButton) }} </template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label for="theme-selector">
|
<label for="theme-selector">
|
||||||
<span class="label__title">Two-factor authentication</span>
|
<span class="label__title">{{ formatMessage(messages.twoFactorFieldTitle) }}</span>
|
||||||
<span class="label__description">
|
<span class="label__description">{{
|
||||||
Add an additional layer of security to your account during login.
|
formatMessage(messages.twoFactorFieldDescription)
|
||||||
</span>
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<button class="iconified-button" @click="showTwoFactorModal">
|
<button class="iconified-button" @click="showTwoFactorModal">
|
||||||
<template v-if="auth.user.has_totp"> <TrashIcon /> Remove 2FA </template>
|
<template v-if="auth.user.has_totp">
|
||||||
<template v-else> <PlusIcon /> Setup 2FA </template>
|
<TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }}
|
||||||
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label for="theme-selector">
|
<label for="theme-selector">
|
||||||
<span class="label__title">Manage authentication providers</span>
|
<span class="label__title">{{ formatMessage(messages.manageProvidersFieldTitle) }}</span>
|
||||||
<span class="label__description">
|
<span class="label__description">{{
|
||||||
Add or remove sign-on methods from your account, including GitHub, GitLab, Microsoft,
|
formatMessage(messages.manageProvidersFieldDescription)
|
||||||
Discord, Steam, and Google.
|
}}</span>
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<button class="iconified-button" @click="$refs.manageProvidersModal.show()">
|
<button class="iconified-button" @click="$refs.manageProvidersModal.show()">
|
||||||
<SettingsIcon /> Manage providers
|
<SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="data-export" class="universal-card">
|
<section id="data-export" class="universal-card">
|
||||||
<h2>Data export</h2>
|
<h2>{{ formatMessage(messages.dataExportTitle) }}</h2>
|
||||||
<p>
|
<p>{{ formatMessage(messages.dataExportDescription) }}</p>
|
||||||
Request a copy of all your personal data you have uploaded to Modrinth. This may take
|
|
||||||
several minutes to complete.
|
|
||||||
</p>
|
|
||||||
<a v-if="generated" class="iconified-button" :href="generated" download="export.json">
|
<a v-if="generated" class="iconified-button" :href="generated" download="export.json">
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
Download export
|
{{ formatMessage(messages.downloadExportButton) }}
|
||||||
</a>
|
</a>
|
||||||
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData">
|
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData">
|
||||||
<template v-if="generatingExport"> <UpdatedIcon /> Generating export... </template>
|
<template v-if="generatingExport">
|
||||||
<template v-else> <UpdatedIcon /> Generate export </template>
|
<UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }}
|
||||||
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="delete-account" class="universal-card">
|
<section id="delete-account" class="universal-card">
|
||||||
<h2>Delete account</h2>
|
<h2>{{ formatMessage(messages.deleteAccountSectionTitle) }}</h2>
|
||||||
<p>
|
<p>{{ formatMessage(messages.deleteAccountSectionDescription) }}</p>
|
||||||
Once you delete your account, there is no going back. Deleting your account will remove all
|
|
||||||
attached data, excluding projects, from our servers.
|
|
||||||
</p>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="iconified-button danger-button"
|
class="iconified-button danger-button"
|
||||||
@click="$refs.modal_confirm.show()"
|
@click="$refs.modal_confirm.show()"
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Delete account
|
{{ formatMessage(messages.deleteAccountButton) }}
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -419,7 +459,15 @@ import {
|
|||||||
UpdatedIcon,
|
UpdatedIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { ConfirmModal, injectNotificationManager, StyledInput } from '@modrinth/ui'
|
import {
|
||||||
|
commonMessages,
|
||||||
|
ConfirmModal,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
|
StyledInput,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import KeyIcon from 'assets/icons/auth/key.svg'
|
import KeyIcon from 'assets/icons/auth/key.svg'
|
||||||
import DiscordIcon from 'assets/icons/auth/sso-discord.svg'
|
import DiscordIcon from 'assets/icons/auth/sso-discord.svg'
|
||||||
import GithubIcon from 'assets/icons/auth/sso-github.svg'
|
import GithubIcon from 'assets/icons/auth/sso-github.svg'
|
||||||
@@ -432,10 +480,6 @@ import QrcodeVue from 'qrcode.vue'
|
|||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
import { getAuthUrl, removeAuthProvider } from '~/composables/auth.js'
|
import { getAuthUrl, removeAuthProvider } from '~/composables/auth.js'
|
||||||
|
|
||||||
useHead({
|
|
||||||
title: 'Account settings - Modrinth',
|
|
||||||
})
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
})
|
})
|
||||||
@@ -443,6 +487,282 @@ definePageMeta({
|
|||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
deleteAccountConfirmTitle: {
|
||||||
|
id: 'settings.account.delete.confirm.title',
|
||||||
|
defaultMessage: 'Are you sure you want to delete your account?',
|
||||||
|
},
|
||||||
|
deleteAccountConfirmDescription: {
|
||||||
|
id: 'settings.account.delete.confirm.description',
|
||||||
|
defaultMessage:
|
||||||
|
'This will **immediately delete all of your user data and follows**. This will not delete your projects. Deleting your account cannot be reversed.<br><br>If you need help with your account, get support on the [Modrinth Discord](https://discord.modrinth.com).',
|
||||||
|
},
|
||||||
|
deleteAccountConfirmProceed: {
|
||||||
|
id: 'settings.account.delete.confirm.proceed',
|
||||||
|
defaultMessage: 'Delete this account',
|
||||||
|
},
|
||||||
|
changeEmailHeaderChange: {
|
||||||
|
id: 'settings.account.email.modal.header.change',
|
||||||
|
defaultMessage: 'Change email',
|
||||||
|
},
|
||||||
|
changeEmailHeaderAdd: {
|
||||||
|
id: 'settings.account.email.modal.header.add',
|
||||||
|
defaultMessage: 'Add email',
|
||||||
|
},
|
||||||
|
emailNotPublicNotice: {
|
||||||
|
id: 'settings.account.email.modal.notice',
|
||||||
|
defaultMessage: 'Your account information is not displayed publicly.',
|
||||||
|
},
|
||||||
|
emailAddressLabel: {
|
||||||
|
id: 'settings.account.email.field.label',
|
||||||
|
defaultMessage: 'Email address',
|
||||||
|
},
|
||||||
|
emailAddressPlaceholder: {
|
||||||
|
id: 'settings.account.email.field.placeholder',
|
||||||
|
defaultMessage: 'Enter your email address...',
|
||||||
|
},
|
||||||
|
saveEmailButton: {
|
||||||
|
id: 'settings.account.email.action.save',
|
||||||
|
defaultMessage: 'Save email',
|
||||||
|
},
|
||||||
|
passwordHeaderRemove: {
|
||||||
|
id: 'settings.account.password.modal.header.remove',
|
||||||
|
defaultMessage: 'Remove password',
|
||||||
|
},
|
||||||
|
passwordHeaderChange: {
|
||||||
|
id: 'settings.account.password.modal.header.change',
|
||||||
|
defaultMessage: 'Change password',
|
||||||
|
},
|
||||||
|
passwordHeaderAdd: {
|
||||||
|
id: 'settings.account.password.modal.header.add',
|
||||||
|
defaultMessage: 'Add password',
|
||||||
|
},
|
||||||
|
passwordsDoNotMatchError: {
|
||||||
|
id: 'settings.account.password.error.mismatch',
|
||||||
|
defaultMessage: 'Input passwords do not match!',
|
||||||
|
},
|
||||||
|
confirmPasswordLabel: {
|
||||||
|
id: 'settings.account.password.field.confirm-current.label',
|
||||||
|
defaultMessage: 'Confirm password',
|
||||||
|
},
|
||||||
|
confirmPasswordDescription: {
|
||||||
|
id: 'settings.account.password.field.confirm-current.description',
|
||||||
|
defaultMessage: 'Please enter your password to proceed.',
|
||||||
|
},
|
||||||
|
oldPasswordLabel: {
|
||||||
|
id: 'settings.account.password.field.old.label',
|
||||||
|
defaultMessage: 'Old password',
|
||||||
|
},
|
||||||
|
confirmPasswordPlaceholder: {
|
||||||
|
id: 'settings.account.password.field.confirm-current.placeholder',
|
||||||
|
defaultMessage: 'Confirm password',
|
||||||
|
},
|
||||||
|
oldPasswordPlaceholder: {
|
||||||
|
id: 'settings.account.password.field.old.placeholder',
|
||||||
|
defaultMessage: 'Old password',
|
||||||
|
},
|
||||||
|
newPasswordLabel: {
|
||||||
|
id: 'settings.account.password.field.new.label',
|
||||||
|
defaultMessage: 'New password',
|
||||||
|
},
|
||||||
|
newPasswordPlaceholder: {
|
||||||
|
id: 'settings.account.password.field.new.placeholder',
|
||||||
|
defaultMessage: 'New password',
|
||||||
|
},
|
||||||
|
confirmNewPasswordLabel: {
|
||||||
|
id: 'settings.account.password.field.confirm-new.label',
|
||||||
|
defaultMessage: 'Confirm new password',
|
||||||
|
},
|
||||||
|
confirmNewPasswordPlaceholder: {
|
||||||
|
id: 'settings.account.password.field.confirm-new.placeholder',
|
||||||
|
defaultMessage: 'Confirm new password',
|
||||||
|
},
|
||||||
|
removePasswordButton: {
|
||||||
|
id: 'settings.account.password.action.remove',
|
||||||
|
defaultMessage: 'Remove password',
|
||||||
|
},
|
||||||
|
savePasswordButton: {
|
||||||
|
id: 'settings.account.password.action.save',
|
||||||
|
defaultMessage: 'Save password',
|
||||||
|
},
|
||||||
|
accountSecurityTitle: {
|
||||||
|
id: 'settings.account.security.title',
|
||||||
|
defaultMessage: 'Account security',
|
||||||
|
},
|
||||||
|
emailFieldTitle: {
|
||||||
|
id: 'settings.account.security.email.title',
|
||||||
|
defaultMessage: 'Email',
|
||||||
|
},
|
||||||
|
emailFieldDescription: {
|
||||||
|
id: 'settings.account.security.email.description',
|
||||||
|
defaultMessage: 'Changes the email associated with your account.',
|
||||||
|
},
|
||||||
|
changeEmailButton: {
|
||||||
|
id: 'settings.account.security.email.action.change',
|
||||||
|
defaultMessage: 'Change email',
|
||||||
|
},
|
||||||
|
addEmailButton: {
|
||||||
|
id: 'settings.account.security.email.action.add',
|
||||||
|
defaultMessage: 'Add email',
|
||||||
|
},
|
||||||
|
passwordFieldTitle: {
|
||||||
|
id: 'settings.account.security.password.title',
|
||||||
|
defaultMessage: 'Password',
|
||||||
|
},
|
||||||
|
passwordDescriptionChange: {
|
||||||
|
id: 'settings.account.security.password.description.change',
|
||||||
|
defaultMessage: 'Change the password used to login to your account.',
|
||||||
|
},
|
||||||
|
passwordDescriptionChangeOrRemove: {
|
||||||
|
id: 'settings.account.security.password.description.change-or-remove',
|
||||||
|
defaultMessage: 'Change or remove the password used to login to your account.',
|
||||||
|
},
|
||||||
|
passwordDescriptionSet: {
|
||||||
|
id: 'settings.account.security.password.description.set',
|
||||||
|
defaultMessage: 'Set a permanent password to login to your account.',
|
||||||
|
},
|
||||||
|
changePasswordButton: {
|
||||||
|
id: 'settings.account.security.password.action.change',
|
||||||
|
defaultMessage: 'Change password',
|
||||||
|
},
|
||||||
|
addPasswordButton: {
|
||||||
|
id: 'settings.account.security.password.action.add',
|
||||||
|
defaultMessage: 'Add password',
|
||||||
|
},
|
||||||
|
twoFactorFieldTitle: {
|
||||||
|
id: 'settings.account.security.two-factor.title',
|
||||||
|
defaultMessage: 'Two-factor authentication',
|
||||||
|
},
|
||||||
|
twoFactorFieldDescription: {
|
||||||
|
id: 'settings.account.security.two-factor.description',
|
||||||
|
defaultMessage: 'Add an additional layer of security to your account during login.',
|
||||||
|
},
|
||||||
|
twoFactorSetupButton: {
|
||||||
|
id: 'settings.account.security.two-factor.action.setup',
|
||||||
|
defaultMessage: 'Setup 2FA',
|
||||||
|
},
|
||||||
|
twoFactorEnterCodeLabel: {
|
||||||
|
id: 'settings.account.two-factor.field.code.label',
|
||||||
|
defaultMessage: 'Enter two-factor code',
|
||||||
|
},
|
||||||
|
twoFactorEnterCodeDescription: {
|
||||||
|
id: 'settings.account.two-factor.field.code.description',
|
||||||
|
defaultMessage: 'Please enter a two-factor code to proceed.',
|
||||||
|
},
|
||||||
|
twoFactorCodePlaceholder: {
|
||||||
|
id: 'settings.account.two-factor.field.code.placeholder',
|
||||||
|
defaultMessage: 'Enter code...',
|
||||||
|
},
|
||||||
|
twoFactorIncorrectError: {
|
||||||
|
id: 'settings.account.two-factor.error.incorrect-code',
|
||||||
|
defaultMessage: 'The code entered is incorrect!',
|
||||||
|
},
|
||||||
|
twoFactorRemoveButton: {
|
||||||
|
id: 'settings.account.security.two-factor.action.remove',
|
||||||
|
defaultMessage: 'Remove 2FA',
|
||||||
|
},
|
||||||
|
twoFactorSetupIntro: {
|
||||||
|
id: 'settings.account.two-factor.setup.intro',
|
||||||
|
defaultMessage:
|
||||||
|
'Two-factor authentication keeps your account secure by requiring access to a second device in order to sign in.',
|
||||||
|
},
|
||||||
|
twoFactorSetupScan: {
|
||||||
|
id: 'settings.account.two-factor.setup.scan',
|
||||||
|
defaultMessage:
|
||||||
|
'Scan the QR code with <authy-link>Authy</authy-link>, <microsoft-authenticator-link>Microsoft Authenticator</microsoft-authenticator-link>, or any other 2FA app to begin.',
|
||||||
|
},
|
||||||
|
twoFactorManualSecretPrefix: {
|
||||||
|
id: 'settings.account.two-factor.setup.manual-secret',
|
||||||
|
defaultMessage: 'If the QR code does not scan, you can manually enter the secret:',
|
||||||
|
},
|
||||||
|
twoFactorVerifyCodeLabel: {
|
||||||
|
id: 'settings.account.two-factor.verify.label',
|
||||||
|
defaultMessage: 'Verify code',
|
||||||
|
},
|
||||||
|
twoFactorVerifyCodeDescription: {
|
||||||
|
id: 'settings.account.two-factor.verify.description',
|
||||||
|
defaultMessage: 'Enter the one-time code from authenticator to verify access.',
|
||||||
|
},
|
||||||
|
twoFactorBackupCodesIntro: {
|
||||||
|
id: 'settings.account.two-factor.backup.intro',
|
||||||
|
defaultMessage:
|
||||||
|
'Download and save these back-up codes in a safe place. You can use these in-place of a 2FA code if you ever lose access to your device! You should protect these codes like your password.',
|
||||||
|
},
|
||||||
|
twoFactorBackupCodesSingleUse: {
|
||||||
|
id: 'settings.account.two-factor.backup.single-use',
|
||||||
|
defaultMessage: 'Backup codes can only be used once.',
|
||||||
|
},
|
||||||
|
completeSetupButton: {
|
||||||
|
id: 'settings.account.button.complete-setup',
|
||||||
|
defaultMessage: 'Complete setup',
|
||||||
|
},
|
||||||
|
manageProvidersModalHeader: {
|
||||||
|
id: 'settings.account.providers.modal.header',
|
||||||
|
defaultMessage: 'Authentication providers',
|
||||||
|
},
|
||||||
|
providersTableProvider: {
|
||||||
|
id: 'settings.account.providers.table.provider',
|
||||||
|
defaultMessage: 'Provider',
|
||||||
|
},
|
||||||
|
providersTableActions: {
|
||||||
|
id: 'settings.account.providers.table.actions',
|
||||||
|
defaultMessage: 'Actions',
|
||||||
|
},
|
||||||
|
providerAddButton: {
|
||||||
|
id: 'settings.account.providers.action.add',
|
||||||
|
defaultMessage: 'Add',
|
||||||
|
},
|
||||||
|
manageProvidersFieldTitle: {
|
||||||
|
id: 'settings.account.security.providers.title',
|
||||||
|
defaultMessage: 'Manage authentication providers',
|
||||||
|
},
|
||||||
|
manageProvidersFieldDescription: {
|
||||||
|
id: 'settings.account.security.providers.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Add or remove sign-on methods from your account, including GitHub, GitLab, Microsoft, Discord, Steam, and Google.',
|
||||||
|
},
|
||||||
|
manageProvidersButton: {
|
||||||
|
id: 'settings.account.security.providers.action.manage',
|
||||||
|
defaultMessage: 'Manage providers',
|
||||||
|
},
|
||||||
|
dataExportTitle: {
|
||||||
|
id: 'settings.account.data-export.title',
|
||||||
|
defaultMessage: 'Data export',
|
||||||
|
},
|
||||||
|
dataExportDescription: {
|
||||||
|
id: 'settings.account.data-export.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Request a copy of all your personal data you have uploaded to Modrinth. This may take several minutes to complete.',
|
||||||
|
},
|
||||||
|
downloadExportButton: {
|
||||||
|
id: 'settings.account.data-export.action.download',
|
||||||
|
defaultMessage: 'Download export',
|
||||||
|
},
|
||||||
|
generatingExportButton: {
|
||||||
|
id: 'settings.account.data-export.action.generating',
|
||||||
|
defaultMessage: 'Generating export...',
|
||||||
|
},
|
||||||
|
generateExportButton: {
|
||||||
|
id: 'settings.account.data-export.action.generate',
|
||||||
|
defaultMessage: 'Generate export',
|
||||||
|
},
|
||||||
|
deleteAccountSectionTitle: {
|
||||||
|
id: 'settings.account.delete.section.title',
|
||||||
|
defaultMessage: 'Delete account',
|
||||||
|
},
|
||||||
|
deleteAccountSectionDescription: {
|
||||||
|
id: 'settings.account.delete.section.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Once you delete your account, there is no going back. Deleting your account will remove all attached data, excluding projects, from our servers.',
|
||||||
|
},
|
||||||
|
deleteAccountButton: {
|
||||||
|
id: 'settings.account.delete.section.action',
|
||||||
|
defaultMessage: 'Delete account',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const changeEmailModal = ref()
|
const changeEmailModal = ref()
|
||||||
const email = ref(auth.value.user.email)
|
const email = ref(auth.value.user.email)
|
||||||
async function saveEmail() {
|
async function saveEmail() {
|
||||||
@@ -462,7 +782,7 @@ async function saveEmail() {
|
|||||||
await useAuth(auth.value.token)
|
await useAuth(auth.value.token)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -501,7 +821,7 @@ async function savePassword() {
|
|||||||
await useAuth(auth.value.token)
|
await useAuth(auth.value.token)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -537,7 +857,7 @@ async function showTwoFactorModal() {
|
|||||||
twoFactorFlow.value = res.flow
|
twoFactorFlow.value = res.flow
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -626,7 +946,7 @@ async function deleteAccount() {
|
|||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -655,7 +975,7 @@ async function exportData() {
|
|||||||
generated.value = URL.createObjectURL(blob)
|
generated.value = URL.createObjectURL(blob)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
commonMessages,
|
||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
CopyCode,
|
CopyCode,
|
||||||
@@ -283,10 +284,14 @@ definePageMeta({
|
|||||||
})
|
})
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Applications - Modrinth',
|
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
headTitle: {
|
||||||
|
id: 'settings.applications.head-title',
|
||||||
|
defaultMessage: 'Applications',
|
||||||
|
},
|
||||||
modalHeader: {
|
modalHeader: {
|
||||||
id: 'settings.applications.modal.header',
|
id: 'settings.applications.modal.header',
|
||||||
defaultMessage: 'Application information',
|
defaultMessage: 'Application information',
|
||||||
@@ -413,10 +418,6 @@ const messages = defineMessages({
|
|||||||
id: 'settings.applications.notification.icon-updated.description',
|
id: 'settings.applications.notification.icon-updated.description',
|
||||||
defaultMessage: 'Your application icon has been updated.',
|
defaultMessage: 'Your application icon has been updated.',
|
||||||
},
|
},
|
||||||
errorTitle: {
|
|
||||||
id: 'settings.applications.notification.error.title',
|
|
||||||
defaultMessage: 'An error occurred',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const { scopesToLabels } = useScopes()
|
const { scopesToLabels } = useScopes()
|
||||||
@@ -585,7 +586,7 @@ async function createApp() {
|
|||||||
await refresh()
|
await refresh()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: formatMessage(messages.errorTitle),
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -649,7 +650,7 @@ async function editApp() {
|
|||||||
appModal.value.hide()
|
appModal.value.hide()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: formatMessage(messages.errorTitle),
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -669,7 +670,7 @@ async function removeApp() {
|
|||||||
editingId.value = null
|
editingId.value = null
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: formatMessage(messages.errorTitle),
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,19 +2,17 @@
|
|||||||
<div class="universal-card">
|
<div class="universal-card">
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
ref="modal_confirm"
|
ref="modal_confirm"
|
||||||
title="Are you sure you want to revoke this application?"
|
:title="formatMessage(messages.revokeConfirmTitle)"
|
||||||
description="This will revoke the application's access to your account. You can always re-authorize it later."
|
:description="formatMessage(messages.revokeConfirmDescription)"
|
||||||
proceed-label="Revoke"
|
:proceed-label="formatMessage(messages.revokeAction)"
|
||||||
@proceed="revokeApp(revokingId)"
|
@proceed="revokeApp(revokingId)"
|
||||||
/>
|
/>
|
||||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.authorizedApps) }}</h2>
|
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.authorizedApps) }}</h2>
|
||||||
<p>
|
<p>
|
||||||
When you authorize an application with your Modrinth account, you grant it access to your
|
{{ formatMessage(messages.description) }}
|
||||||
account. You can manage and review access to your account here at any time.
|
|
||||||
</p>
|
</p>
|
||||||
<div v-if="appInfoLookup.length === 0" class="universal-card recessed">
|
<div v-if="appInfoLookup.length === 0" class="universal-card recessed">
|
||||||
We currently can't display your authorized apps, we're working to fix this. Please visit this
|
{{ formatMessage(messages.emptyState) }}
|
||||||
page at a later date!
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="authorization in appInfoLookup"
|
v-for="authorization in appInfoLookup"
|
||||||
@@ -30,7 +28,7 @@
|
|||||||
{{ authorization.app.name }}
|
{{ authorization.app.name }}
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
<div>
|
||||||
by
|
{{ formatMessage(messages.byLabel) }}
|
||||||
<nuxt-link class="text-link" :to="'/user/' + authorization.owner.id">{{
|
<nuxt-link class="text-link" :to="'/user/' + authorization.owner.id">{{
|
||||||
authorization.owner.username
|
authorization.owner.username
|
||||||
}}</nuxt-link>
|
}}</nuxt-link>
|
||||||
@@ -47,13 +45,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<template v-if="authorization.app.description">
|
<template v-if="authorization.app.description">
|
||||||
<label for="app-description">
|
<label for="app-description">
|
||||||
<span class="label__title"> About this app </span>
|
<span class="label__title">{{ formatMessage(messages.aboutThisAppLabel) }}</span>
|
||||||
</label>
|
</label>
|
||||||
<div id="app-description">{{ authorization.app.description }}</div>
|
<div id="app-description">{{ authorization.app.description }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<label for="app-scope-list">
|
<label for="app-scope-list">
|
||||||
<span class="label__title">Scopes</span>
|
<span class="label__title">{{ formatMessage(commonMessages.scopesLabel) }}</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="scope-list">
|
<div class="scope-list">
|
||||||
<div
|
<div
|
||||||
@@ -82,7 +80,7 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Revoke
|
{{ formatMessage(messages.revokeAction) }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,8 +91,10 @@ import { CheckIcon, TrashIcon } from '@modrinth/assets'
|
|||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
|
commonMessages,
|
||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
|
defineMessages,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
@@ -107,6 +107,44 @@ const client = injectModrinthClient()
|
|||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
headTitle: {
|
||||||
|
id: 'settings.authorizations.head-title',
|
||||||
|
defaultMessage: 'Authorizations',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
id: 'settings.authorizations.description',
|
||||||
|
defaultMessage:
|
||||||
|
'When you authorize an application with your Modrinth account, you grant it access to your account. You can manage and review access to your account here at any time.',
|
||||||
|
},
|
||||||
|
emptyState: {
|
||||||
|
id: 'settings.authorizations.empty-state',
|
||||||
|
defaultMessage:
|
||||||
|
"We currently can't display your authorized apps, we're working to fix this. Please visit this page at a later date!",
|
||||||
|
},
|
||||||
|
revokeConfirmTitle: {
|
||||||
|
id: 'settings.authorizations.revoke.confirm.title',
|
||||||
|
defaultMessage: 'Are you sure you want to revoke this application?',
|
||||||
|
},
|
||||||
|
revokeConfirmDescription: {
|
||||||
|
id: 'settings.authorizations.revoke.confirm.description',
|
||||||
|
defaultMessage:
|
||||||
|
"This will revoke the application's access to your account. You can always re-authorize it later.",
|
||||||
|
},
|
||||||
|
revokeAction: {
|
||||||
|
id: 'settings.authorizations.revoke.action',
|
||||||
|
defaultMessage: 'Revoke',
|
||||||
|
},
|
||||||
|
byLabel: {
|
||||||
|
id: 'settings.authorizations.by',
|
||||||
|
defaultMessage: 'by',
|
||||||
|
},
|
||||||
|
aboutThisAppLabel: {
|
||||||
|
id: 'settings.authorizations.about-this-app',
|
||||||
|
defaultMessage: 'About this app',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const { scopesToDefinitions } = useScopes()
|
const { scopesToDefinitions } = useScopes()
|
||||||
|
|
||||||
const revokingId = ref(null)
|
const revokingId = ref(null)
|
||||||
@@ -116,7 +154,7 @@ definePageMeta({
|
|||||||
})
|
})
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Authorizations - Modrinth',
|
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: usersApps, refetch: refresh } = useQuery({
|
const { data: usersApps, refetch: refresh } = useQuery({
|
||||||
@@ -159,7 +197,7 @@ async function revokeApp(id) {
|
|||||||
await refresh()
|
await refresh()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
current-title="Past charges"
|
:current-title="formatMessage(messages.title)"
|
||||||
:link-stack="[{ href: '/settings/billing', label: 'Billing and subscriptions' }]"
|
:link-stack="[
|
||||||
|
{ href: '/settings/billing', label: formatMessage(commonSettingsMessages.billing) },
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
<h2>Past charges</h2>
|
<h2>{{ formatMessage(messages.title) }}</h2>
|
||||||
<p>All of your past charges to your Modrinth account will be listed here:</p>
|
<p>{{ formatMessage(messages.description) }}</p>
|
||||||
<div
|
<div
|
||||||
v-for="charge in charges"
|
v-for="charge in charges"
|
||||||
:key="charge.id"
|
:key="charge.id"
|
||||||
@@ -15,11 +17,13 @@
|
|||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<span class="font-bold text-primary">
|
<span class="font-bold text-primary">
|
||||||
<template v-if="charge.product?.metadata?.type === 'midas'"> Modrinth Plus </template>
|
<template v-if="charge.product?.metadata?.type === 'midas'">
|
||||||
<template v-else-if="charge.product?.metadata?.type === 'pyro'">
|
{{ formatMessage(messages.productMidas) }}
|
||||||
Modrinth Hosting
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else> Medal Server Trial </template>
|
<template v-else-if="charge.product?.metadata?.type === 'pyro'">
|
||||||
|
{{ formatMessage(messages.productPyro) }}
|
||||||
|
</template>
|
||||||
|
<template v-else> {{ formatMessage(messages.productMedalTrial) }} </template>
|
||||||
<template v-if="charge.subscription_interval">
|
<template v-if="charge.subscription_interval">
|
||||||
{{ charge.subscription_interval }}
|
{{ charge.subscription_interval }}
|
||||||
</template>
|
</template>
|
||||||
@@ -41,6 +45,8 @@
|
|||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
commonSettingsMessages,
|
||||||
|
defineMessages,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
useFormatDateTime,
|
useFormatDateTime,
|
||||||
useFormatPrice,
|
useFormatPrice,
|
||||||
@@ -53,6 +59,7 @@ definePageMeta({
|
|||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
const client = injectModrinthClient()
|
const client = injectModrinthClient()
|
||||||
|
|
||||||
const formatPrice = useFormatPrice()
|
const formatPrice = useFormatPrice()
|
||||||
@@ -62,6 +69,25 @@ const formatDate = useFormatDateTime({
|
|||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
description: {
|
||||||
|
id: 'settings.billing.charges.description',
|
||||||
|
defaultMessage: 'All of your past charges to your Modrinth account will be listed here:',
|
||||||
|
},
|
||||||
|
productMidas: {
|
||||||
|
id: 'settings.billing.charges.product.midas',
|
||||||
|
defaultMessage: 'Modrinth Plus',
|
||||||
|
},
|
||||||
|
productPyro: {
|
||||||
|
id: 'settings.billing.charges.product.pyro',
|
||||||
|
defaultMessage: 'Modrinth Hosting',
|
||||||
|
},
|
||||||
|
productMedalTrial: {
|
||||||
|
id: 'settings.billing.charges.product.medal-trial',
|
||||||
|
defaultMessage: 'Medal Server Trial',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const { data: charges } = useQuery({
|
const { data: charges } = useQuery({
|
||||||
queryKey: ['billing', 'payments'],
|
queryKey: ['billing', 'payments'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
|||||||
@@ -14,34 +14,36 @@
|
|||||||
<div class="flex flex-wrap justify-between gap-4">
|
<div class="flex flex-wrap justify-between gap-4">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<template v-if="midasCharge">
|
<template v-if="midasCharge">
|
||||||
<span v-if="midasCharge.status === 'open'"> You're currently subscribed to: </span>
|
<span v-if="midasCharge.status === 'open'">
|
||||||
|
{{ formatMessage(messages.midasStatusOpen) }}
|
||||||
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'processing'" class="text-orange">
|
<span v-else-if="midasCharge.status === 'processing'" class="text-orange">
|
||||||
Your payment is being processed. Perks will activate once payment is complete.
|
{{ formatMessage(messages.midasStatusProcessing) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'cancelled'">
|
<span v-else-if="midasCharge.status === 'cancelled'">
|
||||||
You've cancelled your subscription. <br />
|
{{ formatMessage(messages.midasStatusCancelledLine1) }} <br />
|
||||||
You will retain your perks until the end of the current billing cycle.
|
{{ formatMessage(messages.midasStatusCancelledLine2) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'failed'" class="text-red">
|
<span v-else-if="midasCharge.status === 'failed'" class="text-red">
|
||||||
Your subscription payment failed. Please update your payment method.
|
{{ formatMessage(messages.midasStatusFailed) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<span v-else>Become a subscriber to Modrinth Plus!</span>
|
<span v-else>{{ formatMessage(messages.midasUpsell) }}</span>
|
||||||
<ModrinthPlusIcon class="h-8 w-min" />
|
<ModrinthPlusIcon class="h-8 w-min" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<span class="font-bold">Benefits</span>
|
<span class="font-bold">{{ formatMessage(messages.midasBenefitsTitle) }}</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CheckCircleIcon class="h-5 w-5 shrink-0 text-brand" />
|
<CheckCircleIcon class="h-5 w-5 shrink-0 text-brand" />
|
||||||
<span> Ad-free browsing on modrinth.com and Modrinth App </span>
|
<span>{{ formatMessage(messages.midasBenefitAdFree) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CheckCircleIcon class="h-5 w-5 shrink-0 text-brand" />
|
<CheckCircleIcon class="h-5 w-5 shrink-0 text-brand" />
|
||||||
<span>Modrinth+ badge on your profile</span>
|
<span>{{ formatMessage(messages.midasBenefitBadge) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CheckCircleIcon class="h-5 w-5 shrink-0 text-brand" />
|
<CheckCircleIcon class="h-5 w-5 shrink-0 text-brand" />
|
||||||
<span>Support Modrinth and creators directly</span>
|
<span>{{ formatMessage(messages.midasBenefitSupport) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,17 +52,22 @@
|
|||||||
<span class="text-2xl font-bold text-dark">
|
<span class="text-2xl font-bold text-dark">
|
||||||
<template v-if="midasCharge">
|
<template v-if="midasCharge">
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatMessage(messages.pricePerInterval, {
|
||||||
midasSubscriptionPrice.prices.intervals[midasSubscription.interval],
|
price: formatPrice(
|
||||||
midasSubscriptionPrice.currency_code,
|
midasSubscriptionPrice.prices.intervals[midasSubscription.interval],
|
||||||
)
|
midasSubscriptionPrice.currency_code,
|
||||||
|
),
|
||||||
|
interval: getIntervalNounLabel(midasSubscription.interval),
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/
|
|
||||||
{{ midasSubscription.interval }}
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ formatPrice(price.prices.intervals.monthly, price.currency_code) }}
|
{{
|
||||||
/ month
|
formatMessage(messages.pricePerInterval, {
|
||||||
|
price: formatPrice(price.prices.intervals.monthly, price.currency_code),
|
||||||
|
interval: formatMessage(messages.intervalMonth),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<!-- Next charge preview for Midas when interval is changing -->
|
<!-- Next charge preview for Midas when interval is changing -->
|
||||||
@@ -74,32 +81,56 @@
|
|||||||
"
|
"
|
||||||
class="-mt-1 flex items-baseline gap-2 text-sm text-secondary"
|
class="-mt-1 flex items-baseline gap-2 text-sm text-secondary"
|
||||||
>
|
>
|
||||||
<span class="opacity-70">Next:</span>
|
<span class="opacity-70">{{ formatMessage(messages.nextLabel) }}</span>
|
||||||
<span class="font-semibold text-contrast">
|
<span class="font-semibold text-contrast">
|
||||||
{{ formatPrice(midasCharge.amount, midasCharge.currency_code) }}
|
{{ formatPrice(midasCharge.amount, midasCharge.currency_code) }}
|
||||||
</span>
|
</span>
|
||||||
<span>/{{ midasCharge.subscription_interval.replace('ly', '') }}</span>
|
<span>
|
||||||
|
{{
|
||||||
|
formatMessage(messages.slashInterval, {
|
||||||
|
interval: getIntervalNounLabel(midasCharge.subscription_interval),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="midasCharge">
|
<template v-if="midasCharge">
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
midasCharge.status === 'open' && midasCharge.subscription_interval === 'monthly'
|
midasCharge.status === 'open' &&
|
||||||
|
midasCharge.subscription_interval === 'monthly' &&
|
||||||
|
oppositePrice != null
|
||||||
"
|
"
|
||||||
class="text-sm text-purple"
|
class="text-sm text-purple"
|
||||||
>
|
>
|
||||||
Save
|
|
||||||
{{
|
{{
|
||||||
formatPrice(midasCharge.amount * 12 - oppositePrice, midasCharge.currency_code)
|
formatMessage(messages.savePerYearBySwitchingToYearly, {
|
||||||
}}/year by switching to yearly billing!
|
amount: formatPrice(
|
||||||
|
midasCharge.amount * 12 - oppositePrice,
|
||||||
|
midasCharge.currency_code,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Since {{ formatDate(midasSubscription.created) }}
|
{{
|
||||||
|
formatMessage(messages.sinceDate, {
|
||||||
|
date: formatDate(midasSubscription.created),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="midasCharge.status === 'open'" class="text-sm text-secondary">
|
<span v-if="midasCharge.status === 'open'" class="text-sm text-secondary">
|
||||||
Renews {{ formatDate(midasCharge.due) }}
|
{{
|
||||||
|
formatMessage(messages.renewsDate, {
|
||||||
|
date: formatDate(midasCharge.due),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'cancelled'" class="text-sm text-secondary">
|
<span v-else-if="midasCharge.status === 'cancelled'" class="text-sm text-secondary">
|
||||||
Expires {{ formatDate(midasCharge.due) }}
|
{{
|
||||||
|
formatMessage(messages.expiresDate, {
|
||||||
|
date: formatDate(midasCharge.due),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
@@ -110,17 +141,25 @@
|
|||||||
"
|
"
|
||||||
class="text-sm text-secondary"
|
class="text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Switches to {{ midasCharge.subscription_interval }} billing on
|
{{
|
||||||
{{ formatDate(midasCharge.due) }}
|
formatMessage(messages.switchesToBillingOn, {
|
||||||
|
interval: getIntervalAdjectiveLabel(midasCharge.subscription_interval),
|
||||||
|
date: formatDate(midasCharge.due),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<span v-else class="text-sm text-secondary">
|
<span v-else class="text-sm text-secondary">
|
||||||
Or
|
|
||||||
{{ formatPrice(price.prices.intervals.yearly, price.currency_code) }} / year (save
|
|
||||||
{{
|
{{
|
||||||
calculateSavings(price.prices.intervals.monthly, price.prices.intervals.yearly)
|
formatMessage(messages.orYearlySave, {
|
||||||
}}%)!
|
price: formatPrice(price.prices.intervals.yearly, price.currency_code),
|
||||||
|
percent: calculateSavings(
|
||||||
|
price.prices.intervals.monthly,
|
||||||
|
price.prices.intervals.yearly,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -136,7 +175,7 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<UpdatedIcon />
|
<UpdatedIcon />
|
||||||
Update method
|
{{ formatMessage(messages.updateMethod) }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled type="transparent" circular>
|
<ButtonStyled type="transparent" circular>
|
||||||
@@ -153,7 +192,9 @@
|
|||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<MoreVerticalIcon />
|
<MoreVerticalIcon />
|
||||||
<template #cancel><XIcon /> Cancel</template>
|
<template #cancel
|
||||||
|
><XIcon /> {{ formatMessage(commonMessages.cancelButton) }}</template
|
||||||
|
>
|
||||||
</OverflowMenu>
|
</OverflowMenu>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +212,7 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<XIcon /> Cancel
|
<XIcon /> {{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
@@ -181,18 +222,28 @@
|
|||||||
<button
|
<button
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
midasCharge.subscription_interval === 'yearly'
|
midasCharge.subscription_interval === 'yearly'
|
||||||
? `Monthly billing will cost you an additional ${formatPrice(
|
? formatMessage(messages.monthlyBillingAdditionalPerYearTooltip, {
|
||||||
oppositePrice * 12 - midasCharge.amount,
|
amount: formatPrice(
|
||||||
midasCharge.currency_code,
|
oppositePrice * 12 - midasCharge.amount,
|
||||||
)} per year`
|
midasCharge.currency_code,
|
||||||
|
),
|
||||||
|
})
|
||||||
: undefined
|
: undefined
|
||||||
"
|
"
|
||||||
:disabled="changingInterval"
|
:disabled="changingInterval"
|
||||||
@click="switchMidasInterval(oppositeInterval)"
|
@click="switchMidasInterval(oppositeInterval)"
|
||||||
>
|
>
|
||||||
<SpinnerIcon v-if="changingInterval" class="animate-spin" />
|
<SpinnerIcon v-if="changingInterval" class="animate-spin" />
|
||||||
<TransferIcon v-else /> {{ changingInterval ? 'Switching' : 'Switch' }} to
|
<TransferIcon v-else />
|
||||||
{{ oppositeInterval }}
|
{{
|
||||||
|
changingInterval
|
||||||
|
? formatMessage(messages.switchingToInterval, {
|
||||||
|
interval: getIntervalAdjectiveLabel(oppositeInterval),
|
||||||
|
})
|
||||||
|
: formatMessage(messages.switchToInterval, {
|
||||||
|
interval: getIntervalAdjectiveLabel(oppositeInterval),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@@ -201,7 +252,7 @@
|
|||||||
color="purple"
|
color="purple"
|
||||||
>
|
>
|
||||||
<button class="ml-auto" @click="cancelSubscription(midasSubscription.id, false)">
|
<button class="ml-auto" @click="cancelSubscription(midasSubscription.id, false)">
|
||||||
Resubscribe <RightArrowIcon />
|
{{ formatMessage(messages.resubscribe) }} <RightArrowIcon />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled v-else color="purple" size="large">
|
<ButtonStyled v-else color="purple" size="large">
|
||||||
@@ -213,7 +264,7 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Subscribe <RightArrowIcon />
|
{{ formatMessage(messages.subscribe) }} <RightArrowIcon />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,31 +287,45 @@
|
|||||||
/>
|
/>
|
||||||
<div v-else class="w-fit">
|
<div v-else class="w-fit">
|
||||||
<p>
|
<p>
|
||||||
A linked server couldn't be found for this subscription. There are a few possible
|
{{ formatMessage(messages.pyroLinkedServerNotFound) }}
|
||||||
explanations for this. If you just purchased your server, this is normal. It could
|
|
||||||
take up to an hour for your server to be provisioned. Otherwise, if you purchased
|
|
||||||
this server a while ago, it has likely since been suspended. If this is not what
|
|
||||||
you were expecting, please contact Modrinth Support with the following
|
|
||||||
information:
|
|
||||||
</p>
|
</p>
|
||||||
<div class="flex w-full flex-col gap-2">
|
<div class="flex w-full flex-col gap-2">
|
||||||
<CopyCode
|
<CopyCode
|
||||||
class="whitespace-nowrap"
|
class="whitespace-nowrap"
|
||||||
:text="'Server ID: ' + subscription.metadata.id"
|
:text="
|
||||||
|
formatMessage(messages.pyroServerIdLabel, {
|
||||||
|
id: subscription.metadata.id,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<CopyCode
|
||||||
|
class="whitespace-nowrap"
|
||||||
|
:text="
|
||||||
|
formatMessage(messages.pyroStripeIdLabel, {
|
||||||
|
id: subscription.id,
|
||||||
|
})
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<CopyCode class="whitespace-nowrap" :text="'Stripe ID: ' + subscription.id" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="m-0 mt-4 text-xl font-semibold leading-none text-contrast">
|
<h3 class="m-0 mt-4 text-xl font-semibold leading-none text-contrast">
|
||||||
{{ getProductSize(getPyroProduct(subscription)) }} Plan
|
{{
|
||||||
|
formatMessage(messages.planTitle, {
|
||||||
|
size: getProductSize(getPyroProduct(subscription)),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-row justify-between">
|
||||||
<div class="mt-2 flex flex-col gap-2">
|
<div class="mt-2 flex flex-col gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CheckCircleIcon class="h-5 w-5 text-brand" />
|
<CheckCircleIcon class="h-5 w-5 text-brand" />
|
||||||
<span>
|
<span>
|
||||||
{{ getPyroProduct(subscription)?.metadata?.cpu / 2 }} Shared CPUs (Bursts up
|
{{
|
||||||
to {{ getPyroProduct(subscription)?.metadata?.cpu }} CPUs)
|
formatMessage(messages.pyroCpuLine, {
|
||||||
|
shared: getPyroProduct(subscription)?.metadata?.cpu / 2,
|
||||||
|
bursts: getPyroProduct(subscription)?.metadata?.cpu,
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@@ -268,7 +333,9 @@
|
|||||||
<span>
|
<span>
|
||||||
{{
|
{{
|
||||||
getPyroProduct(subscription)?.metadata?.ram
|
getPyroProduct(subscription)?.metadata?.ram
|
||||||
? getPyroProduct(subscription).metadata.ram / 1024 + ' GB RAM'
|
? formatMessage(messages.pyroRamLine, {
|
||||||
|
gb: getPyroProduct(subscription).metadata.ram / 1024,
|
||||||
|
})
|
||||||
: ''
|
: ''
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
@@ -278,7 +345,9 @@
|
|||||||
<span>
|
<span>
|
||||||
{{
|
{{
|
||||||
getPyroProduct(subscription)?.metadata?.swap
|
getPyroProduct(subscription)?.metadata?.swap
|
||||||
? getPyroProduct(subscription).metadata.swap / 1024 + ' GB Swap'
|
? formatMessage(messages.pyroSwapLine, {
|
||||||
|
gb: getPyroProduct(subscription).metadata.swap / 1024,
|
||||||
|
})
|
||||||
: ''
|
: ''
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
@@ -288,7 +357,9 @@
|
|||||||
<span>
|
<span>
|
||||||
{{
|
{{
|
||||||
getPyroProduct(subscription)?.metadata?.storage
|
getPyroProduct(subscription)?.metadata?.storage
|
||||||
? getPyroProduct(subscription).metadata.storage / 1024 + ' GB SSD'
|
? formatMessage(messages.pyroStorageLine, {
|
||||||
|
gb: getPyroProduct(subscription).metadata.storage / 1024,
|
||||||
|
})
|
||||||
: ''
|
: ''
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
@@ -309,7 +380,13 @@
|
|||||||
: ''
|
: ''
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>/{{ subscription.interval.replace('ly', '') }}</span>
|
<span>
|
||||||
|
{{
|
||||||
|
formatMessage(messages.slashInterval, {
|
||||||
|
interval: getIntervalNounLabel(subscription.interval),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
@@ -323,7 +400,7 @@
|
|||||||
"
|
"
|
||||||
class="-mt-1 flex items-baseline gap-2 text-sm text-secondary"
|
class="-mt-1 flex items-baseline gap-2 text-sm text-secondary"
|
||||||
>
|
>
|
||||||
<span class="opacity-70">Next:</span>
|
<span class="opacity-70">{{ formatMessage(messages.nextLabel) }}</span>
|
||||||
<span class="font-semibold text-contrast">
|
<span class="font-semibold text-contrast">
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatPrice(
|
||||||
@@ -333,24 +410,33 @@
|
|||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
/
|
|
||||||
{{
|
{{
|
||||||
(
|
formatMessage(messages.slashInterval, {
|
||||||
getPyroCharge(subscription).subscription_interval ||
|
interval: getIntervalNounLabel(
|
||||||
subscription.interval
|
getPyroCharge(subscription).subscription_interval ||
|
||||||
).replace('ly', '')
|
subscription.interval,
|
||||||
|
),
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="getPyroCharge(subscription)" class="mb-4 flex flex-col items-end">
|
<div v-if="getPyroCharge(subscription)" class="mb-4 flex flex-col items-end">
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Since {{ formatDate(subscription.created) }}
|
{{
|
||||||
|
formatMessage(messages.sinceDate, {
|
||||||
|
date: formatDate(subscription.created),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="getPyroCharge(subscription).status === 'open'"
|
v-if="getPyroCharge(subscription).status === 'open'"
|
||||||
class="text-sm text-secondary"
|
class="text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Renews {{ formatDate(getPyroCharge(subscription).due) }}
|
{{
|
||||||
|
formatMessage(messages.renewsDate, {
|
||||||
|
date: formatDate(getPyroCharge(subscription).due),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
@@ -361,30 +447,36 @@
|
|||||||
"
|
"
|
||||||
class="text-sm text-secondary"
|
class="text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Switches to
|
{{
|
||||||
{{ getPyroCharge(subscription).subscription_interval }}
|
formatMessage(messages.switchesToBillingOn, {
|
||||||
billing on
|
interval: getIntervalAdjectiveLabel(
|
||||||
{{ formatDate(getPyroCharge(subscription).due) }}
|
getPyroCharge(subscription).subscription_interval,
|
||||||
|
),
|
||||||
|
date: formatDate(getPyroCharge(subscription).due),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="getPyroCharge(subscription).status === 'processing'"
|
v-else-if="getPyroCharge(subscription).status === 'processing'"
|
||||||
class="text-sm text-orange"
|
class="text-sm text-orange"
|
||||||
>
|
>
|
||||||
Your payment is being processed. Your server will activate once payment is
|
{{ formatMessage(messages.pyroStatusProcessing) }}
|
||||||
complete.
|
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="getPyroCharge(subscription).status === 'cancelled'"
|
v-else-if="getPyroCharge(subscription).status === 'cancelled'"
|
||||||
class="text-sm text-secondary"
|
class="text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Expires {{ formatDate(getPyroCharge(subscription).due) }}
|
{{
|
||||||
|
formatMessage(messages.expiresDate, {
|
||||||
|
date: formatDate(getPyroCharge(subscription).due),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="getPyroCharge(subscription).status === 'failed'"
|
v-else-if="getPyroCharge(subscription).status === 'failed'"
|
||||||
class="text-sm text-red"
|
class="text-sm text-red"
|
||||||
>
|
>
|
||||||
Your subscription payment failed. Please update your payment method, then
|
{{ formatMessage(messages.pyroStatusFailed) }}
|
||||||
resubscribe.
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -397,7 +489,7 @@
|
|||||||
>
|
>
|
||||||
<button @click="showCancellationSurvey(subscription)">
|
<button @click="showCancellationSurvey(subscription)">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
{{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
@@ -411,7 +503,7 @@
|
|||||||
>
|
>
|
||||||
<button @click="showPyroUpgradeModal(subscription)">
|
<button @click="showPyroUpgradeModal(subscription)">
|
||||||
<ArrowBigUpDashIcon />
|
<ArrowBigUpDashIcon />
|
||||||
Upgrade
|
{{ formatMessage(messages.upgrade) }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
@@ -430,7 +522,7 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Resubscribe <RightArrowIcon />
|
{{ formatMessage(messages.resubscribe) }} <RightArrowIcon />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@@ -463,7 +555,7 @@
|
|||||||
:on-error="
|
:on-error="
|
||||||
(err) =>
|
(err) =>
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: err.message ?? (err.data ? err.data.description : err),
|
text: err.message ?? (err.data ? err.data.description : err),
|
||||||
})
|
})
|
||||||
@@ -735,8 +827,205 @@ const messages = defineMessages({
|
|||||||
id: 'settings.billing.pyro_subscription.description',
|
id: 'settings.billing.pyro_subscription.description',
|
||||||
defaultMessage: 'Manage your Modrinth Server subscriptions.',
|
defaultMessage: 'Manage your Modrinth Server subscriptions.',
|
||||||
},
|
},
|
||||||
|
intervalMonth: {
|
||||||
|
id: 'settings.billing.interval.month',
|
||||||
|
defaultMessage: 'month',
|
||||||
|
},
|
||||||
|
intervalYear: {
|
||||||
|
id: 'settings.billing.interval.year',
|
||||||
|
defaultMessage: 'year',
|
||||||
|
},
|
||||||
|
intervalMonthly: {
|
||||||
|
id: 'settings.billing.interval.monthly',
|
||||||
|
defaultMessage: 'monthly',
|
||||||
|
},
|
||||||
|
intervalYearly: {
|
||||||
|
id: 'settings.billing.interval.yearly',
|
||||||
|
defaultMessage: 'yearly',
|
||||||
|
},
|
||||||
|
pricePerInterval: {
|
||||||
|
id: 'settings.billing.price.per-interval',
|
||||||
|
defaultMessage: '{price} / {interval}',
|
||||||
|
},
|
||||||
|
slashInterval: {
|
||||||
|
id: 'settings.billing.price.slash-interval',
|
||||||
|
defaultMessage: '/{interval}',
|
||||||
|
},
|
||||||
|
nextLabel: {
|
||||||
|
id: 'settings.billing.next',
|
||||||
|
defaultMessage: 'Next:',
|
||||||
|
},
|
||||||
|
midasStatusOpen: {
|
||||||
|
id: 'settings.billing.midas.status.open',
|
||||||
|
defaultMessage: "You're currently subscribed to:",
|
||||||
|
},
|
||||||
|
midasStatusProcessing: {
|
||||||
|
id: 'settings.billing.midas.status.processing',
|
||||||
|
defaultMessage:
|
||||||
|
'Your payment is being processed. Perks will activate once payment is complete.',
|
||||||
|
},
|
||||||
|
midasStatusCancelledLine1: {
|
||||||
|
id: 'settings.billing.midas.status.cancelled.line1',
|
||||||
|
defaultMessage: "You've cancelled your subscription.",
|
||||||
|
},
|
||||||
|
midasStatusCancelledLine2: {
|
||||||
|
id: 'settings.billing.midas.status.cancelled.line2',
|
||||||
|
defaultMessage: 'You will retain your perks until the end of the current billing cycle.',
|
||||||
|
},
|
||||||
|
midasStatusFailed: {
|
||||||
|
id: 'settings.billing.midas.status.failed',
|
||||||
|
defaultMessage: 'Your subscription payment failed. Please update your payment method.',
|
||||||
|
},
|
||||||
|
midasUpsell: {
|
||||||
|
id: 'settings.billing.midas.upsell',
|
||||||
|
defaultMessage: 'Become a subscriber to Modrinth Plus!',
|
||||||
|
},
|
||||||
|
midasBenefitsTitle: {
|
||||||
|
id: 'settings.billing.midas.benefits.title',
|
||||||
|
defaultMessage: 'Benefits',
|
||||||
|
},
|
||||||
|
midasBenefitAdFree: {
|
||||||
|
id: 'settings.billing.midas.benefits.ad-free',
|
||||||
|
defaultMessage: 'Ad-free browsing on modrinth.com and Modrinth App',
|
||||||
|
},
|
||||||
|
midasBenefitBadge: {
|
||||||
|
id: 'settings.billing.midas.benefits.badge',
|
||||||
|
defaultMessage: 'Modrinth+ badge on your profile',
|
||||||
|
},
|
||||||
|
midasBenefitSupport: {
|
||||||
|
id: 'settings.billing.midas.benefits.support',
|
||||||
|
defaultMessage: 'Support Modrinth and creators directly',
|
||||||
|
},
|
||||||
|
savePerYearBySwitchingToYearly: {
|
||||||
|
id: 'settings.billing.midas.save-per-year',
|
||||||
|
defaultMessage: 'Save {amount}/year by switching to yearly billing!',
|
||||||
|
},
|
||||||
|
sinceDate: {
|
||||||
|
id: 'settings.billing.since',
|
||||||
|
defaultMessage: 'Since {date}',
|
||||||
|
},
|
||||||
|
renewsDate: {
|
||||||
|
id: 'settings.billing.renews',
|
||||||
|
defaultMessage: 'Renews {date}',
|
||||||
|
},
|
||||||
|
expiresDate: {
|
||||||
|
id: 'settings.billing.expires',
|
||||||
|
defaultMessage: 'Expires {date}',
|
||||||
|
},
|
||||||
|
switchesToBillingOn: {
|
||||||
|
id: 'settings.billing.switches-to-billing-on',
|
||||||
|
defaultMessage: 'Switches to {interval} billing on {date}',
|
||||||
|
},
|
||||||
|
orYearlySave: {
|
||||||
|
id: 'settings.billing.or-yearly-save',
|
||||||
|
defaultMessage: 'Or {price} / year (save {percent}%)!',
|
||||||
|
},
|
||||||
|
updateMethod: {
|
||||||
|
id: 'settings.billing.update-method',
|
||||||
|
defaultMessage: 'Update method',
|
||||||
|
},
|
||||||
|
switchToInterval: {
|
||||||
|
id: 'settings.billing.switch.to-interval',
|
||||||
|
defaultMessage: 'Switch to {interval}',
|
||||||
|
},
|
||||||
|
switchingToInterval: {
|
||||||
|
id: 'settings.billing.switch.switching-to-interval',
|
||||||
|
defaultMessage: 'Switching to {interval}',
|
||||||
|
},
|
||||||
|
monthlyBillingAdditionalPerYearTooltip: {
|
||||||
|
id: 'settings.billing.switch.tooltip.monthly-additional-per-year',
|
||||||
|
defaultMessage: 'Monthly billing will cost you an additional {amount} per year',
|
||||||
|
},
|
||||||
|
resubscribe: {
|
||||||
|
id: 'settings.billing.resubscribe',
|
||||||
|
defaultMessage: 'Resubscribe',
|
||||||
|
},
|
||||||
|
subscribe: {
|
||||||
|
id: 'settings.billing.subscribe',
|
||||||
|
defaultMessage: 'Subscribe',
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
id: 'settings.billing.upgrade',
|
||||||
|
defaultMessage: 'Upgrade',
|
||||||
|
},
|
||||||
|
pyroLinkedServerNotFound: {
|
||||||
|
id: 'settings.billing.pyro.linked-server.not-found',
|
||||||
|
defaultMessage:
|
||||||
|
"A linked server couldn't be found for this subscription. There are a few possible explanations for this. If you just purchased your server, this is normal. It could take up to an hour for your server to be provisioned. Otherwise, if you purchased this server a while ago, it has likely since been suspended. If this is not what you were expecting, please contact Modrinth Support with the following information:",
|
||||||
|
},
|
||||||
|
pyroServerIdLabel: {
|
||||||
|
id: 'settings.billing.pyro.linked-server.server-id',
|
||||||
|
defaultMessage: 'Server ID: {id}',
|
||||||
|
},
|
||||||
|
pyroStripeIdLabel: {
|
||||||
|
id: 'settings.billing.pyro.linked-server.stripe-id',
|
||||||
|
defaultMessage: 'Stripe ID: {id}',
|
||||||
|
},
|
||||||
|
planTitle: {
|
||||||
|
id: 'settings.billing.plan.title',
|
||||||
|
defaultMessage: '{size} Plan',
|
||||||
|
},
|
||||||
|
pyroCpuLine: {
|
||||||
|
id: 'settings.billing.pyro.cpu',
|
||||||
|
defaultMessage: '{shared} Shared CPUs (Bursts up to {bursts} CPUs)',
|
||||||
|
},
|
||||||
|
pyroRamLine: {
|
||||||
|
id: 'settings.billing.pyro.ram',
|
||||||
|
defaultMessage: '{gb} GB RAM',
|
||||||
|
},
|
||||||
|
pyroSwapLine: {
|
||||||
|
id: 'settings.billing.pyro.swap',
|
||||||
|
defaultMessage: '{gb} GB Swap',
|
||||||
|
},
|
||||||
|
pyroStorageLine: {
|
||||||
|
id: 'settings.billing.pyro.storage',
|
||||||
|
defaultMessage: '{gb} GB SSD',
|
||||||
|
},
|
||||||
|
pyroStatusProcessing: {
|
||||||
|
id: 'settings.billing.pyro.status.processing',
|
||||||
|
defaultMessage:
|
||||||
|
'Your payment is being processed. Your server will activate once payment is complete.',
|
||||||
|
},
|
||||||
|
pyroStatusFailed: {
|
||||||
|
id: 'settings.billing.pyro.status.failed',
|
||||||
|
defaultMessage:
|
||||||
|
'Your subscription payment failed. Please update your payment method, then resubscribe.',
|
||||||
|
},
|
||||||
|
pyroResubscribeRequestSubmittedTitle: {
|
||||||
|
id: 'settings.billing.pyro.resubscribe.request-submitted.title',
|
||||||
|
defaultMessage: 'Resubscription request submitted',
|
||||||
|
},
|
||||||
|
pyroResubscribeRequestSubmittedText: {
|
||||||
|
id: 'settings.billing.pyro.resubscribe.request-submitted.text',
|
||||||
|
defaultMessage:
|
||||||
|
'If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made.',
|
||||||
|
},
|
||||||
|
pyroResubscribeSuccessText: {
|
||||||
|
id: 'settings.billing.pyro.resubscribe.success.text',
|
||||||
|
defaultMessage: 'Server subscription resubscribed successfully',
|
||||||
|
},
|
||||||
|
pyroResubscribeErrorTitle: {
|
||||||
|
id: 'settings.billing.pyro.resubscribe.error.title',
|
||||||
|
defaultMessage: 'Error resubscribing',
|
||||||
|
},
|
||||||
|
pyroResubscribeErrorText: {
|
||||||
|
id: 'settings.billing.pyro.resubscribe.error.text',
|
||||||
|
defaultMessage: 'An error occurred while resubscribing to your Modrinth server.',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function getIntervalNounLabel(interval) {
|
||||||
|
return interval === 'yearly'
|
||||||
|
? formatMessage(messages.intervalYear)
|
||||||
|
: formatMessage(messages.intervalMonth)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIntervalAdjectiveLabel(interval) {
|
||||||
|
return interval === 'yearly'
|
||||||
|
? formatMessage(messages.intervalYearly)
|
||||||
|
: formatMessage(messages.intervalMonthly)
|
||||||
|
}
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const { data: paymentMethods } = useQuery({
|
const { data: paymentMethods } = useQuery({
|
||||||
@@ -860,7 +1149,7 @@ async function editPaymentMethod(index, primary) {
|
|||||||
await refresh()
|
await refresh()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -875,7 +1164,7 @@ async function removePaymentMethod(index) {
|
|||||||
await refresh()
|
await refresh()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -893,7 +1182,7 @@ async function cancelSubscription(id, cancelled) {
|
|||||||
await refresh()
|
await refresh()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@@ -920,12 +1209,12 @@ const getPyroCharge = (subscription) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getProductSize = (product) => {
|
const getProductSize = (product) => {
|
||||||
if (!product || !product.metadata) return 'Unknown'
|
if (!product || !product.metadata) return formatMessage(commonMessages.planUnknownLabel)
|
||||||
const ramSize = product.metadata.ram
|
const ramSize = product.metadata.ram
|
||||||
if (ramSize === 4096) return 'Small'
|
if (ramSize === 4096) return formatMessage(commonMessages.planSmallLabel)
|
||||||
if (ramSize === 6144) return 'Medium'
|
if (ramSize === 6144) return formatMessage(commonMessages.planMediumLabel)
|
||||||
if (ramSize === 8192) return 'Large'
|
if (ramSize === 8192) return formatMessage(commonMessages.planLargeLabel)
|
||||||
return 'Custom'
|
return formatMessage(commonMessages.planCustomLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProductPrice = (product, interval) => {
|
const getProductPrice = (product, interval) => {
|
||||||
@@ -962,21 +1251,21 @@ const resubscribePyro = async (subscriptionId, wasSuspended) => {
|
|||||||
await refresh()
|
await refresh()
|
||||||
if (wasSuspended) {
|
if (wasSuspended) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'Resubscription request submitted',
|
title: formatMessage(messages.pyroResubscribeRequestSubmittedTitle),
|
||||||
text: 'If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made.',
|
text: formatMessage(messages.pyroResubscribeRequestSubmittedText),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'Success',
|
title: formatMessage(commonMessages.successLabel),
|
||||||
text: 'Server subscription resubscribed successfully',
|
text: formatMessage(messages.pyroResubscribeSuccessText),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'Error resubscribing',
|
title: formatMessage(messages.pyroResubscribeErrorTitle),
|
||||||
text: 'An error occurred while resubscribing to your Modrinth server.',
|
text: formatMessage(messages.pyroResubscribeErrorText),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
class="radio shrink-0"
|
class="radio shrink-0"
|
||||||
/>
|
/>
|
||||||
<RadioButtonIcon v-else class="radio shrink-0" />
|
<RadioButtonIcon v-else class="radio shrink-0" />
|
||||||
List
|
{{ formatMessage(layoutMode.rows) }}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
class="radio shrink-0"
|
class="radio shrink-0"
|
||||||
/>
|
/>
|
||||||
<RadioButtonIcon v-else class="radio shrink-0" />
|
<RadioButtonIcon v-else class="radio shrink-0" />
|
||||||
Grid
|
{{ formatMessage(layoutMode.grid) }}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,12 +194,19 @@ import type { DisplayLocation } from '~/plugins/cosmetics'
|
|||||||
import { isDarkTheme, type Theme } from '~/plugins/theme/index.ts'
|
import { isDarkTheme, type Theme } from '~/plugins/theme/index.ts'
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Display settings - Modrinth',
|
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
headTitle: {
|
||||||
|
id: 'settings.head-title',
|
||||||
|
defaultMessage: 'Display settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const developerModeBanner = defineMessages({
|
const developerModeBanner = defineMessages({
|
||||||
description: {
|
description: {
|
||||||
id: 'settings.display.banner.developer-mode.description',
|
id: 'settings.display.banner.developer-mode.description',
|
||||||
@@ -212,6 +219,32 @@ const developerModeBanner = defineMessages({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const layoutMode = defineMessages({
|
||||||
|
rows: {
|
||||||
|
id: 'settings.display.project-list-layouts.mode.rows',
|
||||||
|
defaultMessage: 'Rows',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
id: 'settings.display.project-list-layouts.mode.grid',
|
||||||
|
defaultMessage: 'Grid',
|
||||||
|
},
|
||||||
|
gallery: {
|
||||||
|
id: 'settings.display.project-list-layouts.mode.gallery',
|
||||||
|
defaultMessage: 'Gallery',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const notifications = defineMessages({
|
||||||
|
developerModeDeactivatedTitle: {
|
||||||
|
id: 'settings.display.notification.developer-mode-deactivated.title',
|
||||||
|
defaultMessage: 'Developer mode deactivated',
|
||||||
|
},
|
||||||
|
developerModeDeactivatedText: {
|
||||||
|
id: 'settings.display.notification.developer-mode-deactivated.text',
|
||||||
|
defaultMessage: 'Developer mode has been disabled',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const colorTheme = defineMessages({
|
const colorTheme = defineMessages({
|
||||||
title: {
|
title: {
|
||||||
id: 'settings.display.theme.title',
|
id: 'settings.display.theme.title',
|
||||||
@@ -370,8 +403,8 @@ function disableDeveloperMode() {
|
|||||||
flags.value.developerMode = !flags.value.developerMode
|
flags.value.developerMode = !flags.value.developerMode
|
||||||
saveFeatureFlags()
|
saveFeatureFlags()
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'Developer mode deactivated',
|
title: formatMessage(notifications.developerModeDeactivatedTitle),
|
||||||
text: 'Developer mode has been disabled',
|
text: formatMessage(notifications.developerModeDeactivatedText),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ const { addNotification } = injectNotificationManager()
|
|||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Profile settings - Modrinth',
|
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||||
})
|
})
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@@ -108,6 +108,10 @@ definePageMeta({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
headTitle: {
|
||||||
|
id: 'settings.profile.head-title',
|
||||||
|
defaultMessage: 'Profile settings',
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
id: 'settings.profile.profile-info',
|
id: 'settings.profile.profile-info',
|
||||||
defaultMessage: 'Profile information',
|
defaultMessage: 'Profile information',
|
||||||
@@ -231,7 +235,7 @@ async function save() {
|
|||||||
avatarUrl.value = auth.value.user.avatar_url
|
avatarUrl.value = auth.value.user.avatar_url
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err
|
text: err
|
||||||
? err.data
|
? err.data
|
||||||
? err.data.description
|
? err.data.description
|
||||||
|
|||||||
@@ -950,6 +950,21 @@
|
|||||||
"label.password": {
|
"label.password": {
|
||||||
"defaultMessage": "Password"
|
"defaultMessage": "Password"
|
||||||
},
|
},
|
||||||
|
"label.plan-custom": {
|
||||||
|
"defaultMessage": "Custom"
|
||||||
|
},
|
||||||
|
"label.plan-large": {
|
||||||
|
"defaultMessage": "Large"
|
||||||
|
},
|
||||||
|
"label.plan-medium": {
|
||||||
|
"defaultMessage": "Medium"
|
||||||
|
},
|
||||||
|
"label.plan-small": {
|
||||||
|
"defaultMessage": "Small"
|
||||||
|
},
|
||||||
|
"label.plan-unknown": {
|
||||||
|
"defaultMessage": "Unknown"
|
||||||
|
},
|
||||||
"label.platform": {
|
"label.platform": {
|
||||||
"defaultMessage": "Platform"
|
"defaultMessage": "Platform"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -450,6 +450,26 @@ export const commonMessages = defineMessages({
|
|||||||
id: 'label.no-items',
|
id: 'label.no-items',
|
||||||
defaultMessage: 'No items',
|
defaultMessage: 'No items',
|
||||||
},
|
},
|
||||||
|
planUnknownLabel: {
|
||||||
|
id: 'label.plan-unknown',
|
||||||
|
defaultMessage: 'Unknown',
|
||||||
|
},
|
||||||
|
planSmallLabel: {
|
||||||
|
id: 'label.plan-small',
|
||||||
|
defaultMessage: 'Small',
|
||||||
|
},
|
||||||
|
planMediumLabel: {
|
||||||
|
id: 'label.plan-medium',
|
||||||
|
defaultMessage: 'Medium',
|
||||||
|
},
|
||||||
|
planLargeLabel: {
|
||||||
|
id: 'label.plan-large',
|
||||||
|
defaultMessage: 'Large',
|
||||||
|
},
|
||||||
|
planCustomLabel: {
|
||||||
|
id: 'label.plan-custom',
|
||||||
|
defaultMessage: 'Custom',
|
||||||
|
},
|
||||||
switchVersionButton: {
|
switchVersionButton: {
|
||||||
id: 'button.switch-version',
|
id: 'button.switch-version',
|
||||||
defaultMessage: 'Switch version',
|
defaultMessage: 'Switch version',
|
||||||
|
|||||||
Reference in New Issue
Block a user