diff --git a/apps/frontend/src/composables/auth/scopes.ts b/apps/frontend/src/composables/auth/scopes.ts
index 2999de9ee8..39f1a0211c 100644
--- a/apps/frontend/src/composables/auth/scopes.ts
+++ b/apps/frontend/src/composables/auth/scopes.ts
@@ -323,6 +323,57 @@ export const scopeMessages = defineMessages({
},
})
+export const scopeCategoryMessages = defineMessages({
+ categoryUserAccount: {
+ id: 'scopes.category.user-account',
+ defaultMessage: 'User account',
+ },
+ categoryProjects: {
+ id: 'scopes.category.projects',
+ defaultMessage: 'Projects',
+ },
+ categoryVersions: {
+ id: 'scopes.category.versions',
+ defaultMessage: 'Versions',
+ },
+ categoryCollections: {
+ id: 'scopes.category.collections',
+ defaultMessage: 'Collections',
+ },
+ categoryOrganizations: {
+ id: 'scopes.category.organizations',
+ defaultMessage: 'Organizations',
+ },
+ categoryReports: {
+ id: 'scopes.category.reports',
+ defaultMessage: 'Reports',
+ },
+ categoryThreads: {
+ id: 'scopes.category.threads',
+ defaultMessage: 'Threads',
+ },
+ categoryPats: {
+ id: 'scopes.category.pats',
+ defaultMessage: 'PATs',
+ },
+ categorySessions: {
+ id: 'scopes.category.sessions',
+ defaultMessage: 'Sessions',
+ },
+ categoryNotifications: {
+ id: 'scopes.category.notifications',
+ defaultMessage: 'Notifications',
+ },
+ categoryPayouts: {
+ id: 'scopes.category.payouts',
+ defaultMessage: 'Payouts',
+ },
+ categoryAnalytics: {
+ id: 'scopes.category.analytics',
+ defaultMessage: 'Analytics',
+ },
+})
+
const scopeDefinitions = [
{
id: 'USER_READ_EMAIL',
diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json
index f9dc3f10b4..736c3bbf40 100644
--- a/apps/frontend/src/locales/en-US/index.json
+++ b/apps/frontend/src/locales/en-US/index.json
@@ -2444,6 +2444,42 @@
"scopes.analytics.label": {
"message": "Read analytics"
},
+ "scopes.category.analytics": {
+ "message": "Analytics"
+ },
+ "scopes.category.collections": {
+ "message": "Collections"
+ },
+ "scopes.category.notifications": {
+ "message": "Notifications"
+ },
+ "scopes.category.organizations": {
+ "message": "Organizations"
+ },
+ "scopes.category.pats": {
+ "message": "PATs"
+ },
+ "scopes.category.payouts": {
+ "message": "Payouts"
+ },
+ "scopes.category.projects": {
+ "message": "Projects"
+ },
+ "scopes.category.reports": {
+ "message": "Reports"
+ },
+ "scopes.category.sessions": {
+ "message": "Sessions"
+ },
+ "scopes.category.threads": {
+ "message": "Threads"
+ },
+ "scopes.category.user-account": {
+ "message": "User account"
+ },
+ "scopes.category.versions": {
+ "message": "Versions"
+ },
"scopes.collectionCreate.description": {
"message": "Create collections"
},
@@ -2741,6 +2777,102 @@
"servers.plan.small.name": {
"message": "Small"
},
+ "settings.applications.about": {
+ "message": "About"
+ },
+ "settings.applications.button.add-more": {
+ "message": "Add more"
+ },
+ "settings.applications.button.add-redirect-uri": {
+ "message": "Add a redirect uri"
+ },
+ "settings.applications.button.cancel": {
+ "message": "Cancel"
+ },
+ "settings.applications.button.create": {
+ "message": "Create app"
+ },
+ "settings.applications.button.delete": {
+ "message": "Delete"
+ },
+ "settings.applications.button.edit": {
+ "message": "Edit"
+ },
+ "settings.applications.button.new": {
+ "message": "New application"
+ },
+ "settings.applications.button.save-changes": {
+ "message": "Save changes"
+ },
+ "settings.applications.button.upload-icon": {
+ "message": "Upload icon"
+ },
+ "settings.applications.client-id": {
+ "message": "Client ID"
+ },
+ "settings.applications.client-secret": {
+ "message": "Client secret"
+ },
+ "settings.applications.created-on": {
+ "message": "Created on {date}"
+ },
+ "settings.applications.delete.confirm.button": {
+ "message": "Delete this application"
+ },
+ "settings.applications.delete.confirm.description": {
+ "message": "This will permanently delete this application and revoke all access tokens. (forever!)"
+ },
+ "settings.applications.delete.confirm.title": {
+ "message": "Are you sure you want to delete this application?"
+ },
+ "settings.applications.description.intro": {
+ "message": "Applications can be used to authenticate Modrinth's users with your products. For more information, see Modrinth's API documentation ."
+ },
+ "settings.applications.field.description": {
+ "message": "Description"
+ },
+ "settings.applications.field.description.placeholder": {
+ "message": "Enter the application's description..."
+ },
+ "settings.applications.field.icon": {
+ "message": "Icon"
+ },
+ "settings.applications.field.name": {
+ "message": "Name"
+ },
+ "settings.applications.field.name.placeholder": {
+ "message": "Enter the application's name..."
+ },
+ "settings.applications.field.redirect-uri.placeholder": {
+ "message": "https://example.com/auth/callback"
+ },
+ "settings.applications.field.redirect-uris": {
+ "message": "Redirect uris"
+ },
+ "settings.applications.field.scopes": {
+ "message": "Scopes"
+ },
+ "settings.applications.field.url": {
+ "message": "URL"
+ },
+ "settings.applications.field.url.placeholder": {
+ "message": "https://example.com"
+ },
+ "settings.applications.modal.header": {
+ "message": "Application information"
+ },
+ "settings.applications.notification.error.title": {
+ "message": "An error occurred"
+ },
+ "settings.applications.notification.icon-updated.description": {
+ "message": "Your application icon has been updated."
+ },
+ "settings.applications.notification.icon-updated.title": {
+ "message": "Icon updated"
+ },
+ "settings.applications.secret.disclaimer": {
+ "message": "Save your secret now, it will be hidden after you leave this page!"
+ },
"settings.billing.modal.cancel.action": {
"message": "Cancel subscription"
},
diff --git a/apps/frontend/src/pages/settings/applications.vue b/apps/frontend/src/pages/settings/applications.vue
index e62624b233..301d13c71c 100644
--- a/apps/frontend/src/pages/settings/applications.vue
+++ b/apps/frontend/src/pages/settings/applications.vue
@@ -2,29 +2,33 @@
-
+
-
Name
+
{{ formatMessage(messages.nameLabel) }}
+
-
Icon
+
{{ formatMessage(messages.iconLabel) }}
+
- URL
+ {{ formatMessage(messages.urlLabel) }}
- Description
+ {{ formatMessage(messages.descriptionLabel) }}
-
Scopes
-
-
(scopesVal = toggleScope(scopesVal, scope))"
- />
+ {{ formatMessage(messages.scopesLabel) }}
+
+
+
+
+ {{ category.name }}
+
+
+ (scopesVal = toggleScope(scopesVal, scope))"
+ />
+
+
- Redirect uris
+ {{ formatMessage(messages.redirectUrisLabel) }}
+
@@ -100,7 +118,7 @@
@@ -144,13 +162,17 @@
}
"
>
-
New Application
+
{{ formatMessage(messages.newApplication) }}
- Applications can be used to authenticate Modrinth's users with your products. For more
- information, see
- Modrinth's API documentation .
+
+
+
+
+
+
+
@@ -158,25 +180,31 @@
{{ app.name }}
-
Created on {{ new Date(app.created).toLocaleDateString() }}
+
+ {{
+ formatMessage(messages.createdOn, {
+ date: new Date(app.created).toLocaleDateString(),
+ })
+ }}
+
- About
+ {{ formatMessage(messages.aboutLabel) }}
- Client ID
+ {{ formatMessage(messages.clientId) }}
- Client Secret
+ {{ formatMessage(messages.clientSecret) }}
- Save your secret now, it will be hidden after you leave this page!
+ {{ formatMessage(messages.secretDisclaimer) }}
@@ -196,7 +224,7 @@
"
>
- Edit
+ {{ formatMessage(messages.edit) }}
- Delete
+ {{ formatMessage(messages.delete) }}
@@ -224,8 +252,11 @@ import {
commonSettingsMessages,
ConfirmModal,
CopyCode,
+ defineMessages,
FileInput,
injectNotificationManager,
+ IntlFormatted,
+ normalizeChildren,
useVIntl,
} from '@modrinth/ui'
@@ -233,6 +264,7 @@ import Modal from '~/components/ui/Modal.vue'
import {
getScopeValue,
hasScope,
+ scopeCategoryMessages,
scopeList,
toggleScope,
useScopes,
@@ -249,8 +281,196 @@ useHead({
title: 'Applications - Modrinth',
})
+const messages = defineMessages({
+ modalHeader: {
+ id: 'settings.applications.modal.header',
+ defaultMessage: 'Application information',
+ },
+ deleteConfirmTitle: {
+ id: 'settings.applications.delete.confirm.title',
+ defaultMessage: 'Are you sure you want to delete this application?',
+ },
+ deleteConfirmDescription: {
+ id: 'settings.applications.delete.confirm.description',
+ defaultMessage:
+ 'This will permanently delete this application and revoke all access tokens. (forever!)',
+ },
+ deleteConfirmButton: {
+ id: 'settings.applications.delete.confirm.button',
+ defaultMessage: 'Delete this application',
+ },
+ nameLabel: {
+ id: 'settings.applications.field.name',
+ defaultMessage: 'Name',
+ },
+ namePlaceholder: {
+ id: 'settings.applications.field.name.placeholder',
+ defaultMessage: "Enter the application's name...",
+ },
+ iconLabel: {
+ id: 'settings.applications.field.icon',
+ defaultMessage: 'Icon',
+ },
+ uploadIcon: {
+ id: 'settings.applications.button.upload-icon',
+ defaultMessage: 'Upload icon',
+ },
+ urlLabel: {
+ id: 'settings.applications.field.url',
+ defaultMessage: 'URL',
+ },
+ urlPlaceholder: {
+ id: 'settings.applications.field.url.placeholder',
+ defaultMessage: 'https://example.com',
+ },
+ descriptionLabel: {
+ id: 'settings.applications.field.description',
+ defaultMessage: 'Description',
+ },
+ descriptionPlaceholder: {
+ id: 'settings.applications.field.description.placeholder',
+ defaultMessage: "Enter the application's description...",
+ },
+ scopesLabel: {
+ id: 'settings.applications.field.scopes',
+ defaultMessage: 'Scopes',
+ },
+ redirectUrisLabel: {
+ id: 'settings.applications.field.redirect-uris',
+ defaultMessage: 'Redirect uris',
+ },
+ redirectUriPlaceholder: {
+ id: 'settings.applications.field.redirect-uri.placeholder',
+ defaultMessage: 'https://example.com/auth/callback',
+ },
+ addMore: {
+ id: 'settings.applications.button.add-more',
+ defaultMessage: 'Add more',
+ },
+ addRedirectUri: {
+ id: 'settings.applications.button.add-redirect-uri',
+ defaultMessage: 'Add a redirect uri',
+ },
+ cancel: {
+ id: 'settings.applications.button.cancel',
+ defaultMessage: 'Cancel',
+ },
+ saveChanges: {
+ id: 'settings.applications.button.save-changes',
+ defaultMessage: 'Save changes',
+ },
+ createApp: {
+ id: 'settings.applications.button.create',
+ defaultMessage: 'Create app',
+ },
+ newApplication: {
+ id: 'settings.applications.button.new',
+ defaultMessage: 'New application',
+ },
+ descriptionIntro: {
+ id: 'settings.applications.description.intro',
+ defaultMessage:
+ "Applications can be used to authenticate Modrinth's users with your products. For more information, see Modrinth's API documentation .",
+ },
+ aboutLabel: {
+ id: 'settings.applications.about',
+ defaultMessage: 'About',
+ },
+ clientId: {
+ id: 'settings.applications.client-id',
+ defaultMessage: 'Client ID',
+ },
+ clientSecret: {
+ id: 'settings.applications.client-secret',
+ defaultMessage: 'Client secret',
+ },
+ secretDisclaimer: {
+ id: 'settings.applications.secret.disclaimer',
+ defaultMessage: 'Save your secret now, it will be hidden after you leave this page!',
+ },
+ createdOn: {
+ id: 'settings.applications.created-on',
+ defaultMessage: 'Created on {date}',
+ },
+ edit: {
+ id: 'settings.applications.button.edit',
+ defaultMessage: 'Edit',
+ },
+ delete: {
+ id: 'settings.applications.button.delete',
+ defaultMessage: 'Delete',
+ },
+ iconUpdatedTitle: {
+ id: 'settings.applications.notification.icon-updated.title',
+ defaultMessage: 'Icon updated',
+ },
+ iconUpdatedDescription: {
+ id: 'settings.applications.notification.icon-updated.description',
+ defaultMessage: 'Your application icon has been updated.',
+ },
+ errorTitle: {
+ id: 'settings.applications.notification.error.title',
+ defaultMessage: 'An error occurred',
+ },
+})
+
const { scopesToLabels } = useScopes()
+const scopeCategories = computed(() => {
+ return [
+ {
+ name: formatMessage(scopeCategoryMessages.categoryUserAccount),
+ scopes: scopeList.filter((s) => s.startsWith('USER_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryProjects),
+ scopes: scopeList.filter((s) => s.startsWith('PROJECT_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryVersions),
+ scopes: scopeList.filter((s) => s.startsWith('VERSION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryCollections),
+ scopes: scopeList.filter((s) => s.startsWith('COLLECTION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryOrganizations),
+ scopes: scopeList.filter((s) => s.startsWith('ORGANIZATION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryReports),
+ scopes: scopeList.filter((s) => s.startsWith('REPORT_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryThreads),
+ scopes: scopeList.filter((s) => s.startsWith('THREAD_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryPats),
+ scopes: scopeList.filter((s) => s.startsWith('PAT_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categorySessions),
+ scopes: scopeList.filter((s) => s.startsWith('SESSION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryNotifications),
+ scopes: scopeList.filter((s) => s.startsWith('NOTIFICATION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryPayouts),
+ scopes: scopeList.filter((s) => s.startsWith('PAYOUTS_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryAnalytics),
+ scopes: scopeList.filter(
+ (s) => s.startsWith('ANALYTICS') || s.startsWith('PERFORM_ANALYTICS'),
+ ),
+ },
+ ].filter((c) => c.scopes.length > 0)
+})
+
const appModal = ref()
// Any apps created in the current state will be stored here
@@ -347,8 +567,8 @@ async function onImageSelection(files) {
}
addNotification({
- title: 'Icon updated',
- text: 'Your application icon has been updated.',
+ title: formatMessage(messages.iconUpdatedTitle),
+ text: formatMessage(messages.iconUpdatedDescription),
type: 'success',
})
}
@@ -377,7 +597,7 @@ async function createApp() {
await refresh()
} catch (err) {
addNotification({
- title: 'An error occurred',
+ title: formatMessage(messages.errorTitle),
text: err.data ? err.data.description : err,
type: 'error',
})
@@ -445,7 +665,7 @@ async function editApp() {
appModal.value.hide()
} catch (err) {
addNotification({
- title: 'An error occurred',
+ title: formatMessage(messages.errorTitle),
text: err.data ? err.data.description : err,
type: 'error',
})
@@ -468,7 +688,7 @@ async function removeApp() {
editingId.value = null
} catch (err) {
addNotification({
- title: 'An error occurred',
+ title: formatMessage(messages.errorTitle),
text: err.data ? err.data.description : err,
type: 'error',
})
@@ -500,17 +720,10 @@ async function removeApp() {
flex-basis: 24rem !important;
}
}
-.checkboxes {
- display: grid;
- column-gap: 0.5rem;
-
- @media screen and (min-width: 432px) {
- grid-template-columns: repeat(2, 1fr);
- }
- @media screen and (min-width: 800px) {
- grid-template-columns: repeat(3, 1fr);
- }
+.scope-items :deep(.checkbox-outer) {
+ white-space: nowrap !important;
+ justify-content: flex-start !important;
}
.icon-submission {
diff --git a/apps/frontend/src/pages/settings/pats.vue b/apps/frontend/src/pages/settings/pats.vue
index 8294f2ae59..026db6854a 100644
--- a/apps/frontend/src/pages/settings/pats.vue
+++ b/apps/frontend/src/pages/settings/pats.vue
@@ -29,16 +29,26 @@
{{ formatMessage(commonMessages.scopesLabel) }}
-
-
+
+
+
+ {{ category.name }}
+
+
+
+
+
-
+
{{ formatMessage(createModalMessages.expiresLabel) }}
@@ -171,7 +181,7 @@
-
+
{
return pats.value.toSorted((a, b) => new Date(b.created) - new Date(a.created))
})
+const scopeCategories = computed(() => {
+ return [
+ {
+ name: formatMessage(scopeCategoryMessages.categoryUserAccount),
+ scopes: scopeList.filter((s) => s.startsWith('USER_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryProjects),
+ scopes: scopeList.filter((s) => s.startsWith('PROJECT_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryVersions),
+ scopes: scopeList.filter((s) => s.startsWith('VERSION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryCollections),
+ scopes: scopeList.filter((s) => s.startsWith('COLLECTION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryOrganizations),
+ scopes: scopeList.filter((s) => s.startsWith('ORGANIZATION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryReports),
+ scopes: scopeList.filter((s) => s.startsWith('REPORT_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryThreads),
+ scopes: scopeList.filter((s) => s.startsWith('THREAD_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryPats),
+ scopes: scopeList.filter((s) => s.startsWith('PAT_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categorySessions),
+ scopes: scopeList.filter((s) => s.startsWith('SESSION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryNotifications),
+ scopes: scopeList.filter((s) => s.startsWith('NOTIFICATION_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryPayouts),
+ scopes: scopeList.filter((s) => s.startsWith('PAYOUTS_')),
+ },
+ {
+ name: formatMessage(scopeCategoryMessages.categoryAnalytics),
+ scopes: scopeList.filter(
+ (s) => s.startsWith('ANALYTICS') || s.startsWith('PERFORM_ANALYTICS'),
+ ),
+ },
+ ].filter((c) => c.scopes.length > 0)
+})
+
async function createPat() {
startLoading()
loading.value = true
@@ -407,17 +473,9 @@ async function removePat(id) {
}