import type { Archon } from '@modrinth/api-client'
import { NuxtModrinthClient } from '@modrinth/api-client'
-import { LinkIcon, LoaderIcon, SettingsIcon, TimerIcon } from '@modrinth/assets'
+import { LinkIcon, SettingsIcon, TimerIcon } from '@modrinth/assets'
import { useStorage } from '@vueuse/core'
import { computed } from 'vue'
@@ -91,6 +91,9 @@ import {
injectModrinthServerContext,
injectNotificationManager,
} from '#ui/providers'
+import { formatLoaderLabel } from '#ui/utils/loaders'
+
+import LoaderIcon from '../icons/LoaderIcon.vue'
type ServerProjectSummary = {
id: string
diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/access.vue b/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/access.vue
index 993e9f08f4..96cc63944a 100644
--- a/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/access.vue
+++ b/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/access.vue
@@ -39,6 +39,7 @@
:roles="roleOptions"
:can-manage-users="canManageUsers"
:permission-denied-message="permissionDeniedMessage"
+ :user-profile-link="props.userProfileLink"
@update-role="updateMemberRole"
@resend-invite="resendInvite"
@cancel-invite="requestCancelInvite"
@@ -127,6 +128,7 @@ import {
type ServerAccessMember,
type ServerAccessRole,
type ServerAccessRoleOption,
+ type ServerAccessUserProfileLink,
} from '#ui/components/servers/access'
import { useVIntl } from '#ui/composables/i18n'
import { useServerPermissions } from '#ui/composables/server-permissions'
@@ -144,6 +146,7 @@ type RoleFilter = ServerAccessRole | 'all'
const props = withDefaults(
defineProps<{
showAuditLogInstances?: boolean
+ userProfileLink?: (username: string) => ServerAccessUserProfileLink
}>(),
{
showAuditLogInstances: false,
diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/audit-log.ts b/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/audit-log.ts
index 9c8ad53549..37a3ba0b2a 100644
--- a/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/audit-log.ts
+++ b/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/audit-log.ts
@@ -260,6 +260,7 @@ export function useAccessAuditLog({
key: group.key,
label: formatMessage(group.label),
icon: group.icon,
+ dividerBefore: true,
},
...group.actions.map((action) => ({
value: action,
diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/messages.ts b/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/messages.ts
index cc5c107efd..208fa478f2 100644
--- a/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/messages.ts
+++ b/packages/ui/src/layouts/wrapped/hosting/manage/[id]/access/messages.ts
@@ -103,19 +103,19 @@ export const accessMessages = defineMessages({
},
inviteCancelledTitle: {
id: 'servers.access-page.notification.invite-cancelled.title',
- defaultMessage: 'Invite cancelled',
+ defaultMessage: 'Invite revoked',
},
inviteCancelledText: {
id: 'servers.access-page.notification.invite-cancelled.text',
- defaultMessage: 'Cancelled the invite for {target}.',
+ defaultMessage: 'Revoked the invite for {target}.',
},
memberRemovedTitle: {
id: 'servers.access-page.notification.member-removed.title',
- defaultMessage: 'Access removed',
+ defaultMessage: 'Access revoked',
},
memberRemovedText: {
id: 'servers.access-page.notification.member-removed.text',
- defaultMessage: 'Removed {target} from this server.',
+ defaultMessage: 'Revoked access for {target}.',
},
loadFailedTitle: {
id: 'servers.access-page.notification.load-failed.title',
@@ -178,7 +178,7 @@ export const actionLogActionMessages = defineMessages({
},
user_removed: {
id: 'servers.access-page.activity-log-filter.action.user-removed',
- defaultMessage: 'Removed user',
+ defaultMessage: 'Revoked access',
},
addon_added: {
id: 'servers.access-page.activity-log-filter.action.addon-added',
diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/backups.vue b/packages/ui/src/layouts/wrapped/hosting/manage/backups.vue
index fc97dcbde0..b6e8da3f92 100644
--- a/packages/ui/src/layouts/wrapped/hosting/manage/backups.vue
+++ b/packages/ui/src/layouts/wrapped/hosting/manage/backups.vue
@@ -156,6 +156,7 @@
operation.operation_type === 'create' && operation.user_info)
+ ?.user_info ?? null
+ )
+}
+
const groupedBackups = computed((): BackupGroup[] => {
if (!filteredBackups.value.length) return []
diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json
index 52faa3251f..6019d51a41 100644
--- a/packages/ui/src/locales/en-US/index.json
+++ b/packages/ui/src/locales/en-US/index.json
@@ -3414,7 +3414,7 @@
"defaultMessage": "Changed user permissions"
},
"servers.access-page.activity-log-filter.action.user-removed": {
- "defaultMessage": "Removed user"
+ "defaultMessage": "Revoked access"
},
"servers.access-page.activity-log-filter.add": {
"defaultMessage": "Add filter"
@@ -3450,10 +3450,10 @@
"defaultMessage": "Friend request could not be sent"
},
"servers.access-page.notification.invite-cancelled.text": {
- "defaultMessage": "Cancelled the invite for {target}."
+ "defaultMessage": "Revoked the invite for {target}."
},
"servers.access-page.notification.invite-cancelled.title": {
- "defaultMessage": "Invite cancelled"
+ "defaultMessage": "Invite revoked"
},
"servers.access-page.notification.invite-failed.title": {
"defaultMessage": "Invite could not be sent"
@@ -3477,10 +3477,10 @@
"defaultMessage": "Access could not be loaded"
},
"servers.access-page.notification.member-removed.text": {
- "defaultMessage": "Removed {target} from this server."
+ "defaultMessage": "Revoked access for {target}."
},
"servers.access-page.notification.member-removed.title": {
- "defaultMessage": "Access removed"
+ "defaultMessage": "Access revoked"
},
"servers.access-page.notification.remove-failed.title": {
"defaultMessage": "Access could not be removed"
@@ -3525,10 +3525,10 @@
"defaultMessage": "Limited"
},
"servers.access-table.action.cancel-invite": {
- "defaultMessage": "Cancel invite"
+ "defaultMessage": "Revoke invite"
},
"servers.access-table.action.remove-user": {
- "defaultMessage": "Remove user"
+ "defaultMessage": "Revoke access"
},
"servers.access-table.action.resend-invite": {
"defaultMessage": "Resend invite"
@@ -3807,7 +3807,7 @@
"defaultMessage": "Changed permissions for to {permissions}"
},
"servers.audit-log.event.user-removed": {
- "defaultMessage": "Removed "
+ "defaultMessage": "Revoked access for "
},
"servers.audit-log.scope.server": {
"defaultMessage": "Server"
@@ -3935,6 +3935,12 @@
"servers.backups.item.backup-schedule": {
"defaultMessage": "Backup schedule"
},
+ "servers.backups.item.creator-avatar-alt": {
+ "defaultMessage": "{username}'s avatar"
+ },
+ "servers.backups.item.creator.support": {
+ "defaultMessage": "Support"
+ },
"servers.backups.item.manual-backup": {
"defaultMessage": "Manual backup"
},
@@ -4440,7 +4446,7 @@
"defaultMessage": "Added {time}"
},
"servers.remove-access-modal.cancel-button": {
- "defaultMessage": "Cancel invite"
+ "defaultMessage": "Revoke invite"
},
"servers.remove-access-modal.cancel-effect-access": {
"defaultMessage": "They will not be added to this server"
@@ -4449,13 +4455,13 @@
"defaultMessage": "You can send them another invite later"
},
"servers.remove-access-modal.cancel-header": {
- "defaultMessage": "Cancel invite"
+ "defaultMessage": "Revoke invite"
},
"servers.remove-access-modal.cancel-warning-body": {
- "defaultMessage": "If you cancel this invite, {username} will need a new invitation before they can join this server."
+ "defaultMessage": "If you revoke this invite, {username} will need a new invitation before they can join this server."
},
"servers.remove-access-modal.header": {
- "defaultMessage": "Remove user"
+ "defaultMessage": "Revoke access"
},
"servers.remove-access-modal.invited-label": {
"defaultMessage": "Invited {time}"
@@ -4464,7 +4470,7 @@
"defaultMessage": "Pending invite"
},
"servers.remove-access-modal.remove-button": {
- "defaultMessage": "Remove user"
+ "defaultMessage": "Revoke access"
},
"servers.remove-access-modal.remove-effect-access": {
"defaultMessage": "They will immediately lose access to the server panel and will no longer be able to edit content"
@@ -4479,7 +4485,7 @@
"defaultMessage": "{username}'s avatar"
},
"servers.remove-access-modal.warning-body": {
- "defaultMessage": "If you remove a user from your server, you'll need to re-invite them to restore access."
+ "defaultMessage": "If you revoke a user's access to your server, you'll need to re-invite them to restore access."
},
"servers.remove-access-modal.what-happens-label": {
"defaultMessage": "What happens?"
diff --git a/packages/ui/src/stories/servers/BackupItem.stories.ts b/packages/ui/src/stories/servers/BackupItem.stories.ts
index 7add6b91fc..279c9b8d53 100644
--- a/packages/ui/src/stories/servers/BackupItem.stories.ts
+++ b/packages/ui/src/stories/servers/BackupItem.stories.ts
@@ -17,6 +17,18 @@ const meta = {
export default meta
type Story = StoryObj
+const creator: Archon.BackupsQueue.v1.UserInfo = {
+ id: 'traben',
+ username: 'Traben',
+ avatar_url: 'https://cdn.modrinth.com/user/6Qo4A5QT/9d81be1a9fb1afd163b7f2f05a791955e7693c90.png',
+}
+
+const supportCreator: Archon.BackupsQueue.v1.UserInfo = {
+ id: 'support',
+ username: 'Support',
+ avatar_url: null,
+}
+
function makeBackup(
overrides: Partial = {},
): Archon.BackupsQueue.v1.BackupQueueBackup {
@@ -36,6 +48,7 @@ export const Default: Story = {
name: 'Default (manual)',
args: {
backup: makeBackup({ name: 'Base finished!!' }),
+ creator,
},
}
@@ -46,6 +59,14 @@ export const Automated: Story = {
},
}
+export const SupportCreated: Story = {
+ name: 'Support created',
+ args: {
+ backup: makeBackup({ name: 'Support recovery point' }),
+ creator: supportCreator,
+ },
+}
+
export const Preview: Story = {
name: 'Preview (compact, used in delete modal)',
args: {
@@ -85,12 +106,16 @@ export const CommonStates: Story = {
return {
manual: makeBackup({ name: 'Base finished!!' }),
+ support: makeBackup({ id: 'backup-support', name: 'Support recovery point' }),
automated: makeBackup({ automated: true, name: 'Backup #2' }),
+ creator,
+ supportCreator,
}
},
template: /* html */ `
-
+
+
diff --git a/packages/ui/src/utils/loaders.ts b/packages/ui/src/utils/loaders.ts
index fc7ada93b3..011c789b91 100644
--- a/packages/ui/src/utils/loaders.ts
+++ b/packages/ui/src/utils/loaders.ts
@@ -9,6 +9,7 @@ export const loaderDisplayNames: Record = {
forge: 'Forge',
quilt: 'Quilt',
paper: 'Paper',
+ spigot: 'Spigot',
purpur: 'Purpur',
bukkit: 'Bukkit',
vanilla: 'Vanilla',
diff --git a/standards/frontend/SURFACE_SYSTEM.md b/standards/frontend/SURFACE_SYSTEM.md
new file mode 100644
index 0000000000..79b656a21a
--- /dev/null
+++ b/standards/frontend/SURFACE_SYSTEM.md
@@ -0,0 +1,25 @@
+# Surface System
+
+Use `surface-*` variables to describe UI elevation and separation. The scale is ordered from the page base up through stronger raised surfaces and strokes.
+
+## Layers
+
+| Token | Use |
+| ----------- | ------------------------------------------------------------------- |
+| `surface-1` | Page background. |
+| `surface-2` | Default raised surfaces, table rows, and standard card backgrounds. |
+| `surface-3` | Header bands, inputs, dropdown surfaces, and card hover states. |
+| `surface-4` | Standard strokes and outlines, including table outlines. |
+| `surface-5` | Strong strokes for surfaces that need extra separation. |
+
+## Strokes
+
+Use `surface-4` for normal outlines and dividers. Tables should use `surface-4` for their outer border and row separators.
+
+Reserve `surface-5` for stronger outlines, such as modal frames, high-emphasis separators, or hover states on elements that already sit on `surface-4`.
+
+## Backgrounds
+
+Use `surface-1` for page backgrounds and `surface-2` for ordinary raised content. Use `surface-3` for header strips, inputs, and temporary elevation such as hover states. Use `surface-4` sparingly as a stronger raised background, usually for controls or badges that need to sit above nearby content.
+
+Avoid using legacy aliased background variables for new UI. Prefer explicit `bg-surface-*` and `border-surface-*` utilities so the layer intent is visible in the component.