diff --git a/.agents/skills/api-module/SKILL.md b/.agents/skills/api-module/SKILL.md new file mode 100644 index 0000000000..32cc16756b --- /dev/null +++ b/.agents/skills/api-module/SKILL.md @@ -0,0 +1,23 @@ +--- +name: api-module +description: Add an API endpoint module to packages/api-client from an OpenAPI schema. Use for new backend endpoints, API client modules, or tasks that provide an OpenAPI schema. +--- + +# Add an API Module + +Read the applicable `AGENTS.md` files before you edit code. + +Read [the API module standard](../../../standards/frontend/ADDING_API_MODULES.md) in full. + +1. Identify the OpenAPI schema from the request. If more than one schema is possible, ask the user to select one. +2. Read the schema. Identify each endpoint, HTTP method, request type, response type, and path parameter. +3. Get the service and version from the URL prefix. For example, map `/v3/projects` to `labrinth/v3/`. +4. Define the API types in `types.ts`. Make each type match the schema exactly. +5. Do not change, rename, or remove API fields. +6. Make a module class that extends `AbstractModule`. Implement each endpoint with `this.client.request()` or `this.client.upload()`. +7. Use the request-option pattern from the standard. Do not call `$fetch`, `fetch`, or another HTTP client directly. +8. Add the module to `MODULE_REGISTRY` so the client can instantiate it. +9. Export new service types from the applicable barrel `index.ts`. +10. Check the module paths, registry key, public type exports, and endpoint types. + +Run only the checks that the user or the applicable `AGENTS.md` permits. diff --git a/.agents/skills/api-module/agents/openai.yaml b/.agents/skills/api-module/agents/openai.yaml new file mode 100644 index 0000000000..c8dd768afe --- /dev/null +++ b/.agents/skills/api-module/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Add API Module" + short_description: "Add typed API client modules from OpenAPI" + default_prompt: "Use $api-module to add an API client module from this OpenAPI schema." diff --git a/.agents/skills/cross-platform-pages/SKILL.md b/.agents/skills/cross-platform-pages/SKILL.md new file mode 100644 index 0000000000..2399604821 --- /dev/null +++ b/.agents/skills/cross-platform-pages/SKILL.md @@ -0,0 +1,38 @@ +--- +name: cross-platform-pages +description: Convert a page to the shared Modrinth page system for the website and desktop app. Use for shared layouts, wrapped layouts, or platform dependency-injection contracts. +--- + +# Convert a Cross-Platform Page + +Read the applicable `AGENTS.md` files before you edit code. + +Read these standards in full: + +- [Cross-platform pages](../../../standards/frontend/CROSS_PLATFORM_PAGES.md) +- [Dependency injection](../../../standards/frontend/DEPENDENCY_INJECTION.md) + +1. Identify the target page from the request. +2. Read the page and its route shell. Identify data sources, mutations, navigation, and platform APIs. +3. Use a wrapped layout when both platforms use the same API source and page logic. +4. Use a shared layout when platform data or operations have different implementations. + +For a shared layout: + +1. Define a provider contract for all platform operations. +2. Put common UI and state logic in the shared layout. +3. Put reusable search, filter, and selection logic in local composables. +4. Implement the contract in `apps/frontend/` and `apps/app-frontend/`. +5. Use optional contract fields only for capabilities that are not available on both platforms. + +For a wrapped layout: + +1. Move the page to `packages/ui/src/layouts/wrapped/` and preserve its route structure. +2. Replace platform-only imports with common utilities or provider calls. +3. Make each frontend route shell render the wrapped component. +4. Match primary query options in both route shells when the layout uses `ReadyTransition` and `useReadyState`. +5. Prefetch these queries with `ensureQueryData`, as the standard specifies. + +Check that both route shells resolve their imports. Check that all required provider fields have implementations. + +Run only the checks that the user or the applicable `AGENTS.md` permits. diff --git a/.agents/skills/cross-platform-pages/agents/openai.yaml b/.agents/skills/cross-platform-pages/agents/openai.yaml new file mode 100644 index 0000000000..ad15d4df68 --- /dev/null +++ b/.agents/skills/cross-platform-pages/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Convert Cross-Platform Page" + short_description: "Share pages across the web and desktop app" + default_prompt: "Use $cross-platform-pages to convert this page for the website and desktop app." diff --git a/.agents/skills/figma-mcp/SKILL.md b/.agents/skills/figma-mcp/SKILL.md new file mode 100644 index 0000000000..b6d99eecca --- /dev/null +++ b/.agents/skills/figma-mcp/SKILL.md @@ -0,0 +1,21 @@ +--- +name: figma-mcp +description: Convert a Figma design into a Modrinth Vue page or component. Use when a request provides a Figma URL or asks to implement a Figma layout. +--- + +# Implement a Figma Design + +Read the applicable `AGENTS.md` files before you edit code. + +Read `packages/ui/AGENTS.md` in full. + +1. Load the available Figma design-to-code instructions and follow the MCP tool guidance. +2. Call `get_design_context` first with `clientLanguages: "typescript,html,css"` and `clientFrameworks: "vue"`. +3. Treat the result as reference code and adapt it to the Modrinth codebase. +4. Map Figma color variables to the applicable `surface-*` and `text-*` tokens. Do not use aliased Figma names directly. +5. Reuse applicable components from `packages/ui/src/components/` before creating new ones. Also refer to `standards/frontend/COMPONENT_STRUCTURE.md` +6. Read `packages/assets/styles/variables.scss` when Figma does not supply a required token. +7. Use exact spacing values from the design. +8. Implement the result as a Vue SFC with Tailwind classes and the existing component library. + +Run only the checks that the user or the applicable `AGENTS.md` permits. diff --git a/.agents/skills/figma-mcp/agents/openai.yaml b/.agents/skills/figma-mcp/agents/openai.yaml new file mode 100644 index 0000000000..1a7c61d304 --- /dev/null +++ b/.agents/skills/figma-mcp/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Implement Figma Design" + short_description: "Build Modrinth Vue UI from Figma designs" + default_prompt: "Use $figma-mcp to implement this Figma design as a Modrinth Vue component." diff --git a/.agents/skills/i18n-pass/SKILL.md b/.agents/skills/i18n-pass/SKILL.md new file mode 100644 index 0000000000..05f69de695 --- /dev/null +++ b/.agents/skills/i18n-pass/SKILL.md @@ -0,0 +1,32 @@ +--- +name: i18n-pass +description: Convert hard-coded English text in changed Vue components to the @modrinth/ui localization system. Use for an i18n pass, untranslated-string review, pull request, or component migration. +--- + +# Do an Internationalization Pass + +Read the applicable `AGENTS.md` files before you edit code. + +Read [the internationalization standard](../../../standards/frontend/INTERNATIONALIZATION.md) in full. + +1. Identify the scope from the request. +2. For a pull request, use `gh pr diff ` to identify changed files. +3. For a file path, inspect that file. +4. When the request gives no scope, inspect the current uncommitted diff. +5. Limit the pass to changed `.vue` files. +6. Find user-visible text in templates and scripts. + +Check inner text, `alt`, `placeholder`, `aria-label`, buttons, tooltips, notifications, dropdown labels, and error messages. + +Do not change dynamic expressions, HTML tag names, CSS classes, internal identifiers, or log messages. + +1. Define stable message IDs with `defineMessage` or `defineMessages`. +2. Replace simple text with `formatMessage()` calls. +3. Use `` for text that contains links or markup. +4. Use ICU selections and plurals when grammar depends on a value. +5. Add a space before `}}` when an ICU placeholder ends at the Vue delimiter. +6. Do not change component logic, layout, or reactivity. +7. Do not edit localization JSON files. The user maintains those files. +8. Check the changed templates again for hard-coded English text. + +Run only the checks that the user or the applicable `AGENTS.md` permits. diff --git a/.agents/skills/i18n-pass/agents/openai.yaml b/.agents/skills/i18n-pass/agents/openai.yaml new file mode 100644 index 0000000000..347ef98b2d --- /dev/null +++ b/.agents/skills/i18n-pass/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Run Internationalization Pass" + short_description: "Localize user-visible text in Vue files" + default_prompt: "Use $i18n-pass to localize the user-visible text in these changed Vue files." diff --git a/.agents/skills/review-changelog/SKILL.md b/.agents/skills/review-changelog/SKILL.md new file mode 100644 index 0000000000..5ab041c1c9 --- /dev/null +++ b/.agents/skills/review-changelog/SKILL.md @@ -0,0 +1,40 @@ +--- +name: review-changelog +description: Review the latest packages/blog/changelog.ts entry against the Modrinth changelog standard. Use before a pull request or when asked to review or lint a changelog entry. +--- + +# Review a Changelog Entry + +Read [the changelog standard](../../../standards/maintaining/CHANGELOG.md) in full before the review. + +1. Open `packages/blog/changelog.ts`. +2. Find the first entry in the `VERSIONS` array. +3. If the request names `web`, `hosting`, or `app`, review the latest entry for that product. +4. Otherwise, review the latest entry and all adjacent entries with the same date. + +Check the entry structure: + +- `date` contains a valid ISO 8601 timestamp. +- `product` is `web`, `hosting`, or `app`. +- An `app` entry has a `version` value. +- A `web` or `hosting` entry does not have a `version` value. +- Standard headings are `## Added`, `## Changed`, `## Fixed`, and `## Security`. +- A featured release can use a linked heading. +- Flag the legacy `## Improvements` heading. + +Check each bullet: + +- The voice and tense agree with the section. +- The first verb agrees with the section. +- The bullet describes user-visible behavior, not implementation. +- The bullet identifies the applicable page, tab, modal, or feature. +- The bullet contains one sentence, uses sentence case, and ends with a period. +- Product and UI names use the public labels. +- The bullet does not contain filler, vague intensifiers, apologies, or internal references. +- The bullet is not a duplicate detail of a larger listed change. + +Group findings by entry. For each finding, show the original bullet and a proposed replacement. + +If the entry has no findings, state this result. Do not edit the changelog unless the user asks you to apply fixes. + +When the user asks for fixes, preserve tab indentation and template-literal formatting. diff --git a/.agents/skills/review-changelog/agents/openai.yaml b/.agents/skills/review-changelog/agents/openai.yaml new file mode 100644 index 0000000000..421afc3587 --- /dev/null +++ b/.agents/skills/review-changelog/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Review Changelog" + short_description: "Review changelog entries for style problems" + default_prompt: "Use $review-changelog to review the latest changelog entry." diff --git a/.agents/skills/tanstack-query/SKILL.md b/.agents/skills/tanstack-query/SKILL.md new file mode 100644 index 0000000000..71c3ecc71b --- /dev/null +++ b/.agents/skills/tanstack-query/SKILL.md @@ -0,0 +1,39 @@ +--- +name: tanstack-query +description: Convert Vue server-state code to TanStack Query. Use for useQuery, useMutation, cache invalidation, optimistic updates, or replacement of useAsyncData and manual ref patterns. +--- + +# Convert Data Code to TanStack Query + +Read the applicable `AGENTS.md` files before you edit code. + +Read [the TanStack Query standard](../../../standards/frontend/FETCHING_DATA.md) in full. + +1. Identify the target file from the request. +2. Find `useAsyncData`, `useFetch`, manual API refs, and fetch calls in `onMounted`. +3. Identify mutations that use manual loading, error, or result refs. + +For queries: + +1. Replace manual fetch logic with `useQuery`. +2. Get `api-client` with `injectModrinthClient()`. +3. Use a hierarchical query key with the resource, qualifier, and parameters. +4. Use a computed query key for reactive parameters. +5. Use a computed `enabled` option when the query depends on other data. +6. Use a shared query-option factory when multiple components use the query. + +For mutations: + +1. Replace manual mutation state with `useMutation`. +2. Invalidate or update related query data after success. +3. Use an optimistic update only when the UI needs an immediate response. +4. Cancel the applicable query and save its prior data before an optimistic update. +5. Restore the prior data after an error. Invalidate the query after settlement. + +Remove manual loading and error refs that TanStack Query replaces. Remove obsolete `onMounted` fetch calls. + +Keep Nuxt SSR behavior. Match route-shell prefetch options when `ReadyTransition` and `useReadyState` depend on the query. + +Check query keys, invalidation prefixes, reactive values, and rollback data. + +Run only the checks that the user or the applicable `AGENTS.md` permits. diff --git a/.agents/skills/tanstack-query/agents/openai.yaml b/.agents/skills/tanstack-query/agents/openai.yaml new file mode 100644 index 0000000000..2db5ece878 --- /dev/null +++ b/.agents/skills/tanstack-query/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Migrate to TanStack Query" + short_description: "Migrate Vue server state to TanStack Query" + default_prompt: "Use $tanstack-query to migrate this Vue component to TanStack Query." diff --git a/.claude/skills/api-module/SKILL.md b/.claude/skills/api-module/SKILL.md deleted file mode 100644 index 37b4ac7788..0000000000 --- a/.claude/skills/api-module/SKILL.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: api-module -description: Add a new API endpoint module to packages/api-client from an OpenAPI schema. Use when adding new backend endpoints, creating API client modules, or when an openapi.yml is provided. -argument-hint: ---- - -Refer to the standard: @standards/frontend/ADDING_API_MODULES.md - -## Steps - -1. **Read the OpenAPI schema** at `$ARGUMENTS` — identify the endpoints, request/response shapes, and path parameters. -2. **Read the standard above** for naming conventions, type rules, and the module registration pattern. -3. **Determine the service and version** — the URL path prefix tells you which service directory and version namespace to use (e.g. `/v3/projects` → `labrinth/v3/`). -4. **Define types in `types.ts`** — types must match the API response 1:1. Use the OpenAPI schema as the source of truth. Do not reshape or rename fields. -5. **Create the module class** — extend `BaseModule`, implement each endpoint as a method. Use the correct HTTP verb and request options pattern from the standard. -6. **Register in `MODULE_REGISTRY`** — add the module entry so it's auto-instantiated on the client. -7. **Export types** from the service's barrel `index.ts`. -8. **Verify** — check that the module compiles and the types are accessible from `@modrinth/api-client`. diff --git a/.claude/skills/cross-platform-pages/SKILL.md b/.claude/skills/cross-platform-pages/SKILL.md deleted file mode 100644 index 6558eed3c3..0000000000 --- a/.claude/skills/cross-platform-pages/SKILL.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: cross-platform-pages -description: Convert a page to the cross-platform page system so it works in both the website and the desktop app. Use when moving a page into packages/ui/src/layouts/, creating shared or wrapped layouts, or setting up DI contracts for platform abstraction. -argument-hint: ---- - -Refer to the standards: @standards/frontend/CROSS_PLATFORM_PAGES.md and @standards/frontend/DEPENDENCY_INJECTION.md - -## Steps - -1. **Read the target page** at `$ARGUMENTS` and understand its data sources, mutations, and navigation. -2. **Read the standards above** to understand the shared vs wrapped distinction and the DI pattern. -3. **Decide the category:** - - **Wrapped** (`layouts/wrapped/`) — if the page uses the same API source on both platforms (e.g. web requests, not Tauri plugins). Just move the page component into `packages/ui` and import it from both frontends. - - **Shared** (`layouts/shared/`) — if the page has different data-fetching logic per platform (e.g. website uses `api-client`, app uses Tauri `invoke`). Requires a DI contract. -4. **For shared layouts:** - - Define a DI contract interface in `providers/` capturing all platform-specific operations. - - Create the layout component that injects the context and handles all UI logic. - - Extract reusable stateful logic (search, filtering, selection) into `composables/`. - - Implement the contract separately in each frontend (`apps/frontend/`, `apps/app-frontend/`). -5. **For wrapped pages:** - - Move the page component into `packages/ui/src/layouts/wrapped/` matching the route structure. - - Replace any platform-specific imports with shared utilities. - - Import and render the wrapped page from both frontends as a simple component. - - If the layout uses TanStack Query for initial route paint with `ReadyTransition` / `useReadyState`, each platform route shell must call `ensureQueryData` for those queries with matching keys and fetchers — see **Platform route shells: prefetch with `ensureQueryData`** in `standards/frontend/CROSS_PLATFORM_PAGES.md`. -6. **Verify** the page renders correctly by checking for missing imports and that all DI contracts are satisfied. diff --git a/.claude/skills/figma-mcp/SKILL.md b/.claude/skills/figma-mcp/SKILL.md deleted file mode 100644 index defabf8d2e..0000000000 --- a/.claude/skills/figma-mcp/SKILL.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: figma-mcp -description: Use the Figma MCP server to translate a Figma design into a Vue page or component layout. Use when the user provides a Figma URL, asks to implement a design, or wants to draft a page layout from Figma. -argument-hint: ---- - -Refer to the standard: @standards/frontend/FIGMA_MCP_USAGE.md -Also read @packages/ui/CLAUDE.md for color token mapping and component conventions. - -## Steps - -1. **Parse the Figma URL** from `$ARGUMENTS` — extract the `fileKey` and `nodeId`. Convert `-` to `:` in the node ID. -2. **Read the standards above** for the available tools, adaptation rules, and color usage. -3. **Call `get_design_context`** with the extracted `nodeId` and `fileKey`, using `clientLanguages: "typescript,html,css"` and `clientFrameworks: "vue"`. This is always the first tool to call. -5. **Adapt the output to the Modrinth codebase:** - - Map Figma color variables to `surface-*` / `text-*` tokens — never use Figma's aliased names directly. - - Check `packages/ui/src/components/` for existing components that match elements in the design (buttons, cards, modals, inputs, etc.). - - Check `packages/assets/styles/variables.scss` for tokens not exposed in Figma. - - Match spacing values exactly from the design. -6. **Use `get_screenshot`** if you need a closer visual reference of specific nodes. -7. **Use `get_variable_defs`** to verify which design tokens are applied to ambiguous elements. -8. **Build the component** as a Vue SFC using Tailwind classes and the project's existing component library. diff --git a/.claude/skills/i18n-pass/SKILL.md b/.claude/skills/i18n-pass/SKILL.md deleted file mode 100644 index 7edd69996c..0000000000 --- a/.claude/skills/i18n-pass/SKILL.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: i18n-pass -description: Perform an i18n localization pass on changed files or a pull request, converting hard-coded English strings to the @modrinth/ui i18n system. Use when internationalizing a set of changes, reviewing a PR for untranslated strings, or converting a specific component. -argument-hint: [file-path-or-pr-number] ---- - -Refer to the standard: @standards/frontend/INTERNATIONALIZATION.md - -## Steps - -1. **Identify the scope of changes:** - - If `$ARGUMENTS` is a PR number, run `gh pr diff $ARGUMENTS` to get the changed files. - - If `$ARGUMENTS` is a file path, use that directly. - - If no argument, check `git diff` for uncommitted changes. -2. **Read the standard above** for the message definition pattern, ICU format rules, and `IntlFormatted` usage. -3. **Filter to Vue SFCs** — only `.vue` files need i18n passes. Skip non-component files. -4. **For each file, scan for hard-coded strings:** - - `
- - - +
@@ -95,14 +97,16 @@ import { import { Accordion, Avatar, - ButtonStyled, + Button, defineMessages, + IconButton, injectNotificationManager, useVIntl, } from '@modrinth/ui' import type { Ref } from 'vue' -import { computed, onUnmounted, ref } from 'vue' +import { computed, ref } from 'vue' +import { useAppEvent } from '@/composables/use-app-event' import { trackEvent } from '@/helpers/analytics' import { get_default_user, @@ -111,7 +115,6 @@ import { set_default_user, users, } from '@/helpers/auth' -import { process_listener } from '@/helpers/events' import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts' import type { Skin } from '@/helpers/skins' import { get_available_skins } from '@/helpers/skins' @@ -248,16 +251,12 @@ async function logout(id: string) { trackEvent('AccountLogOut') } -const unlisten = await process_listener(async (e) => { +useAppEvent('process', async (e) => { if (e.event === 'launched') { await refreshValues() } }) -onUnmounted(() => { - unlisten() -}) - const messages = defineMessages({ notSignedIn: { id: 'minecraft-account.not-signed-in', diff --git a/apps/app-frontend/src/components/ui/AddContentButton.vue b/apps/app-frontend/src/components/ui/AddContentButton.vue index cdea116e87..65f6397a9e 100644 --- a/apps/app-frontend/src/components/ui/AddContentButton.vue +++ b/apps/app-frontend/src/components/ui/AddContentButton.vue @@ -1,6 +1,6 @@ diff --git a/apps/app-frontend/src/components/ui/Breadcrumbs.vue b/apps/app-frontend/src/components/ui/Breadcrumbs.vue index 032a569be0..7d97df4335 100644 --- a/apps/app-frontend/src/components/ui/Breadcrumbs.vue +++ b/apps/app-frontend/src/components/ui/Breadcrumbs.vue @@ -2,7 +2,7 @@
- {{ breadcrumbData.resetToNames(breadcrumbs) }} -
@@ -42,34 +60,13 @@ - - - - diff --git a/apps/frontend/src/components/ui/moderation/checklist/ChecklistKeybindsModal.vue b/apps/frontend/src/components/ui/moderation/checklist/ChecklistKeybindsModal.vue deleted file mode 100644 index 5c0da57c65..0000000000 --- a/apps/frontend/src/components/ui/moderation/checklist/ChecklistKeybindsModal.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - diff --git a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue index 21acd31a13..e5790e3e84 100644 --- a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue +++ b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue @@ -1,5 +1,4 @@
-
+ +
- Shared instance + {{ report.shared_instance?.name ?? 'Shared instance' }} Version {{ report.shared_instance_version_id ?? 'unknown' }} · @@ -83,14 +90,20 @@
- +
@@ -119,12 +132,22 @@ - - diff --git a/apps/frontend/src/pages/[type]/[project]/changelog.vue b/apps/frontend/src/pages/[type]/[project]/changelog.vue index f48956653a..376964b839 100644 --- a/apps/frontend/src/pages/[type]/[project]/changelog.vue +++ b/apps/frontend/src/pages/[type]/[project]/changelog.vue @@ -53,17 +53,18 @@ {{ formatDate(version.date_published) }}
- - - - + +
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets' import { - ButtonStyled, + ButtonLink, injectModrinthClient, injectProjectPageContext, Pagination, @@ -367,8 +368,4 @@ function getVersionLoaders(version) { } } } - -.brand-button { - color: var(--color-accent-contrast); -} diff --git a/apps/frontend/src/pages/[type]/[project]/gallery.vue b/apps/frontend/src/pages/[type]/[project]/gallery.vue index 801a86017d..c4e3f0c677 100644 --- a/apps/frontend/src/pages/[type]/[project]/gallery.vue +++ b/apps/frontend/src/pages/[type]/[project]/gallery.vue @@ -1,5 +1,6 @@
{ + const projectDate = project.value?.queued ?? project.value?.approved ?? project.value?.published + if (thread.value && projectDate && dayjs(projectDate).isBefore(dayjs(THREADS_RELEASE_DATE))) { + const newThread = JSON.parse(JSON.stringify(thread.value)) + newThread.messages.unshift({ + id: '69', + author_id: null, + body: { + type: 'legacy_project_message', + }, + created: THREADS_RELEASE_DATE, + hide_identity: false, + }) + return newThread + } + return thread.value +}) + const canAccess = computed(() => !!currentMember.value) const staff = computed(() => isStaff(currentMember.value?.user)) const userFacingUiVisible = computed( diff --git a/apps/frontend/src/pages/[type]/[project]/settings.vue b/apps/frontend/src/pages/[type]/[project]/settings.vue index 96b7886fd9..19f469effd 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings.vue @@ -6,6 +6,7 @@ import { GlobeIcon, ImageIcon, InfoIcon, + IssuesIcon, LinkIcon, ServerIcon, SignatureIcon, @@ -71,6 +72,11 @@ const navItems = computed(() => { label: formatMessage(commonProjectSettingsMessages.server), icon: ServerIcon, }, + { + link: `/${base}/settings/disclosures`, + label: formatMessage(commonProjectSettingsMessages.disclosures), + icon: IssuesIcon, + }, { link: `/${base}/settings/tags`, label: formatMessage(commonProjectSettingsMessages.tags), @@ -173,7 +179,7 @@ const moderatorSeeUserUi = computed({ @toggle-collapsed="() => (collapsedChecklist = !collapsedChecklist)" @set-processing="setProcessing" /> -
+
diff --git a/apps/frontend/src/pages/[type]/[project]/settings/analytics.vue b/apps/frontend/src/pages/[type]/[project]/settings/analytics.vue index 352e4ffae3..7b3651a0c9 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/analytics.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/analytics.vue @@ -3,5 +3,9 @@ diff --git a/apps/frontend/src/pages/[type]/[project]/settings/description.vue b/apps/frontend/src/pages/[type]/[project]/settings/description.vue index ef168ec53f..6cd1c44bef 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/description.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/description.vue @@ -1,5 +1,6 @@ @@ -27,7 +27,8 @@ diff --git a/apps/frontend/src/pages/[type]/[project]/settings/license.vue b/apps/frontend/src/pages/[type]/[project]/settings/license.vue index 34e2f3564d..fea81bc4ff 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/license.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/license.vue @@ -28,12 +28,13 @@
-
@@ -155,24 +156,28 @@ + diff --git a/apps/frontend/src/pages/[type]/[project]/settings/versions.vue b/apps/frontend/src/pages/[type]/[project]/settings/versions.vue index a0f385a639..c6e29f821b 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/versions.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/versions.vue @@ -35,16 +35,16 @@ > @@ -67,180 +67,189 @@ :open-modal="currentMember ? () => handleOpenCreateVersionModal() : undefined" > @@ -304,11 +313,13 @@
No versions created
Create your first project version.

- - - +
@@ -335,7 +346,8 @@ import { } from '@modrinth/assets' import { Admonition, - ButtonStyled, + Button, + ButtonLink, commonMessages, commonProjectSettingsMessages, ConfirmModal, @@ -343,8 +355,8 @@ import { injectModrinthClient, injectNotificationManager, injectProjectPageContext, - OverflowMenu, ProjectPageVersions, + TeleportOverflowMenu, useVIntl, } from '@modrinth/ui' import { useTemplateRef, watch } from 'vue' @@ -369,6 +381,8 @@ const { cdnDownloadReason, } = injectProjectPageContext() +useProjectSettingsHeadTitle(commonProjectSettingsMessages.versions) + // Load versions on mount (client-side) onMounted(() => { loadVersions() diff --git a/apps/frontend/src/pages/[type]/[project]/version/[version].vue b/apps/frontend/src/pages/[type]/[project]/version/[version].vue index 5facef220d..193aeb89f7 100644 --- a/apps/frontend/src/pages/[type]/[project]/version/[version].vue +++ b/apps/frontend/src/pages/[type]/[project]/version/[version].vue @@ -65,18 +65,19 @@
- - - - - - + +
@@ -104,16 +105,16 @@ > @@ -127,23 +128,39 @@ class="mb-4" >

- - - + + +
- - - - - - + +
@@ -96,16 +98,17 @@ {{ entityLabel }}

- - - + + +
- - - - - - + +
{{ progressLabel }} @@ -145,16 +144,28 @@ v-if="type === 'instance-download' && actions?.length" class="col-start-1 col-end-3 row-start-3 mt-2 flex min-w-0 flex-wrap items-center gap-2" > - - - + + {{ action.label }} +
@@ -182,11 +193,12 @@ import { CheckIcon, SpinnerIcon, XIcon } from '@modrinth/assets' import { computed, ref } from 'vue' +import { Button, IconButton } from '#ui/components/base/buttons' + import { useFormatBytes, useFormatNumber } from '../../composables' import type { PopupNotificationButton, PopupNotificationProgressType } from '../../providers' import { truncatedTooltip } from '../../utils/truncate' import Avatar from '../base/Avatar.vue' -import ButtonStyled from '../base/ButtonStyled.vue' type NotificationToastType = | 'friend-request' diff --git a/packages/ui/src/components/page/NormalPage.vue b/packages/ui/src/components/page/NormalPage.vue index ce3f0e1689..3acc18993e 100644 --- a/packages/ui/src/components/page/NormalPage.vue +++ b/packages/ui/src/components/page/NormalPage.vue @@ -3,6 +3,7 @@ import { injectPageContext } from '@modrinth/ui' defineProps<{ sidebar?: 'right' | 'left' + fullWidth?: boolean }>() const { hierarchicalSidebarAvailable } = injectPageContext() @@ -12,6 +13,7 @@ const { hierarchicalSidebarAvailable } = injectPageContext() :class="{ 'ui-normal-page--sidebar-left': sidebar === 'left' && !hierarchicalSidebarAvailable, 'ui-normal-page--sidebar-right': sidebar === 'right' && !hierarchicalSidebarAvailable, + 'ui-normal-page--full-width': fullWidth, }" >
@@ -46,6 +48,10 @@ const { hierarchicalSidebarAvailable } = injectPageContext() / 100%; } +.ui-normal-page--full-width { + width: calc(100% - 2rem); +} + @media (width >= 64rem) { .ui-normal-page--sidebar-left { grid-template: diff --git a/packages/ui/src/components/project/ArchivedProjectBanner.vue b/packages/ui/src/components/project/ArchivedProjectBanner.vue new file mode 100644 index 0000000000..0cc6b6921e --- /dev/null +++ b/packages/ui/src/components/project/ArchivedProjectBanner.vue @@ -0,0 +1,41 @@ + + + diff --git a/packages/ui/src/components/project/ProjectPageHeader.vue b/packages/ui/src/components/project/ProjectPageHeader.vue index 86fea39c72..d6400ffa62 100644 --- a/packages/ui/src/components/project/ProjectPageHeader.vue +++ b/packages/ui/src/components/project/ProjectPageHeader.vue @@ -9,7 +9,7 @@ @@ -311,16 +323,18 @@
- {{ gameVersion }} + {{ gameVersionGroup.label }}
- {{ gameVersion }} + {{ gameVersionGroup.label }}
@@ -445,7 +460,6 @@ import { } from '@modrinth/assets' import { AutoLink, - ButtonStyled, Pagination, SmartClickable, Table, @@ -457,11 +471,17 @@ import { VersionChannelIndicator, VersionFilterControl, } from '@modrinth/ui' -import { formatVersionsForDisplay, type GameVersionTag, type Version } from '@modrinth/utils' +import { + type GameVersionTag, + getVersionGroupsForDisplay, + type VersionDisplayGroup, +} from '@modrinth/utils' import { Menu } from 'floating-vue' import { computed, type Ref, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' +import { Button } from '#ui/components/base/buttons' + import { useRelativeTime } from '../../composables' import { defineMessages, useVIntl } from '../../composables/i18n' import { formatTag } from '../../utils/tag-messages' @@ -479,15 +499,12 @@ const formatBytes = useFormatBytes() const MAX_GAME_VERSION_TAGS = 5 const MAX_PLATFORM_TAGS = 3 -type VersionWithDisplayUrlEnding = Version & { +type VersionWithDisplayUrlEnding = Labrinth.Versions.v3.Version & { displayUrlEnding: string - environment?: Labrinth.Projects.v3.Environment - mrpack_loaders?: string[] } type DisplayVersion = VersionWithDisplayUrlEnding & { noModLoader: boolean - files_missing_attribution?: boolean } type VersionTableColumn = @@ -515,7 +532,7 @@ const props = withDefaults( currentMember?: boolean loaders: Labrinth.Tags.v2.Loader[] gameVersions: GameVersionTag[] - versionLink?: (version: Version) => string + versionLink?: (version: Labrinth.Versions.v3.Version) => string openModal?: () => void createVersionButtonSecondary?: boolean }>(), @@ -616,8 +633,8 @@ function hasNoModLoader(loaders: string[]): boolean { ) } -function getDisplayGameVersions(version: DisplayVersion): string[] { - return formatVersionsForDisplay(version.game_versions, props.gameVersions) +function getDisplayGameVersions(version: DisplayVersion): VersionDisplayGroup[] { + return getVersionGroupsForDisplay(version.game_versions, props.gameVersions) } function getFilterTooltip(filter: string): string { diff --git a/packages/ui/src/components/project/ProjectSidebarCreators.vue b/packages/ui/src/components/project/ProjectSidebarCreators.vue index 0e58d1b997..1eab4a2d36 100644 --- a/packages/ui/src/components/project/ProjectSidebarCreators.vue +++ b/packages/ui/src/components/project/ProjectSidebarCreators.vue @@ -2,45 +2,59 @@

{{ formatMessage(messages.title) }}

- @@ -80,6 +94,7 @@ const props = defineProps<{ userLink: (username: string) => string linkTarget?: string userLinkTarget?: string | null + loading?: boolean }>() function resolveLinkTarget(target: string | null | undefined): string | null { @@ -113,6 +128,8 @@ const sortedMembers = computed(() => { return owner ? [owner, ...rest] : rest }) +const isEmpty = computed(() => !props.organization && sortedMembers.value.length === 0) + const messages = defineMessages({ title: { id: 'project.about.creators.title', diff --git a/packages/ui/src/components/project/ProjectSidebarDetails.vue b/packages/ui/src/components/project/ProjectSidebarDetails.vue index bfe45a68c3..8be4b125e9 100644 --- a/packages/ui/src/components/project/ProjectSidebarDetails.vue +++ b/packages/ui/src/components/project/ProjectSidebarDetails.vue @@ -13,7 +13,89 @@

{{ formatMessage(commonMessages.detailsLabel) }}

-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- - - + + +
diff --git a/packages/ui/src/components/servers/SaveBanner.vue b/packages/ui/src/components/servers/SaveBanner.vue index afc753fe51..3f4eb45b6f 100644 --- a/packages/ui/src/components/servers/SaveBanner.vue +++ b/packages/ui/src/components/servers/SaveBanner.vue @@ -3,22 +3,29 @@

You have unsaved changes.

- - - - - - - - - + + +
@@ -28,7 +35,7 @@ import { HistoryIcon, SaveIcon, SpinnerIcon } from '@modrinth/assets' import { computed } from 'vue' -import ButtonStyled from '#ui/components/base/ButtonStyled.vue' +import { Button } from '#ui/components/base/buttons' import FloatingActionBar from '#ui/components/base/FloatingActionBar.vue' import { injectModrinthClient, injectModrinthServerContext } from '#ui/providers' diff --git a/packages/ui/src/components/servers/ServerListing.vue b/packages/ui/src/components/servers/ServerListing.vue index fbfe10d460..f1c12c15d8 100644 --- a/packages/ui/src/components/servers/ServerListing.vue +++ b/packages/ui/src/components/servers/ServerListing.vue @@ -183,46 +183,53 @@
- - - - - - - - {{ formatMessage(messages.supportLabel) }} - - - - - {{ formatMessage(messages.manageBillingLabel) }} - - - - - + + + + {{ formatMessage(messages.supportLabel) }} + + + {{ formatMessage(messages.manageBillingLabel) }} + +
@@ -261,11 +268,12 @@ import { SparklesIcon, SpinnerIcon, } from '@modrinth/assets' -import { AutoLink, ButtonStyled } from '@modrinth/ui' import { useQuery } from '@tanstack/vue-query' import { computed, ref } from 'vue' import { useRouter } from 'vue-router' +import { Button, ButtonLink, IconButton } from '#ui/components/base/buttons' + import { CardIcon, CheckIcon, diff --git a/packages/ui/src/components/servers/ServerSettingsModal.vue b/packages/ui/src/components/servers/ServerSettingsModal.vue index d63706a963..6798efeca1 100644 --- a/packages/ui/src/components/servers/ServerSettingsModal.vue +++ b/packages/ui/src/components/servers/ServerSettingsModal.vue @@ -68,6 +68,7 @@ useQuery({ from_modpack: false, }), enabled: computed(() => !!worldId.value), + staleTime: 30_000, }) const serverSettingsTabComponentMap = { diff --git a/packages/ui/src/components/servers/ServerSetupModal.vue b/packages/ui/src/components/servers/ServerSetupModal.vue index bdaba6afa2..528020ca19 100644 --- a/packages/ui/src/components/servers/ServerSetupModal.vue +++ b/packages/ui/src/components/servers/ServerSetupModal.vue @@ -131,6 +131,11 @@ async function onFlowComplete(ctx: CreationFlowContextValue) { await handleMrpackUpload(ctx.modpackFile.value, ctx.buildProperties()) } else if (ctx.setupType.value === 'modpack' && ctx.modpackSelection.value) { debug('onFlowComplete: modpack selection path, calling installContent') + serverContext.beginInstallation({ + type: 'modrinth_modpack', + project_id: ctx.modpackSelection.value.projectId, + version_id: ctx.modpackSelection.value.versionId, + }) await client.archon.content_v1.installContent( serverContext.serverId, serverContext.worldId.value!, @@ -159,6 +164,15 @@ async function onFlowComplete(ctx: CreationFlowContextValue) { apiLoader: toApiLoader(loader ?? 'vanilla'), }) + serverContext.beginInstallation({ + type: 'platform', + platform: (loader ?? 'vanilla') as Extract< + Archon.Websocket.v0.InstallProgressKey, + { type: 'platform' } + >['platform'], + platform_version: loaderVersion, + game_version: ctx.selectedGameVersion.value ?? '', + }) await client.archon.content_v1.installContent( serverContext.serverId, serverContext.worldId.value!, @@ -183,6 +197,7 @@ async function onFlowComplete(ctx: CreationFlowContextValue) { creationFlowRef.value?.hide() } catch (error) { debug('onFlowComplete: ERROR', error) + serverContext.cancelOptimisticInstallation() if ((error as ModrinthApiError).statusCode === 429) { addNotification({ title: formatMessage(messages.rateLimitTitle), @@ -210,6 +225,10 @@ async function handleMrpackUpload(file: File, properties: Archon.Content.v1.Prop { softOverride: false }, ) await uploadProgressModal.value!.track(handle) + serverContext.beginInstallation({ + type: 'local_modpack', + filename: file.name, + }) emitReinstall() } diff --git a/packages/ui/src/components/servers/ServersPromo.vue b/packages/ui/src/components/servers/ServersPromo.vue index ef3990a3f2..0268ff48c9 100644 --- a/packages/ui/src/components/servers/ServersPromo.vue +++ b/packages/ui/src/components/servers/ServersPromo.vue @@ -1,8 +1,7 @@ @@ -402,14 +369,10 @@ function onContentErrorDismiss() { @@ -30,7 +34,7 @@ import { UploadIcon } from '@modrinth/assets' import { computed } from 'vue' import Admonition from '#ui/components/base/Admonition.vue' -import ButtonStyled from '#ui/components/base/ButtonStyled.vue' +import { Button } from '#ui/components/base/buttons' import { useFormatBytes } from '#ui/composables' import { injectModrinthServerContext } from '#ui/providers' diff --git a/packages/ui/src/components/servers/backups/BackupCreateModal.vue b/packages/ui/src/components/servers/backups/BackupCreateModal.vue index 363f428cc7..229f1d3d2e 100644 --- a/packages/ui/src/components/servers/backups/BackupCreateModal.vue +++ b/packages/ui/src/components/servers/backups/BackupCreateModal.vue @@ -46,22 +46,20 @@
@@ -73,6 +71,8 @@ import { IssuesIcon, PlusIcon, XIcon } from '@modrinth/assets' import { useMutation, useQueryClient } from '@tanstack/vue-query' import { computed, nextTick, ref } from 'vue' +import { Button } from '#ui/components/base/buttons' + import { useVIntl } from '../../../composables/i18n' import { injectModrinthClient, @@ -80,7 +80,6 @@ import { injectNotificationManager, } from '../../../providers' import { commonMessages } from '../../../utils' -import ButtonStyled from '../../base/ButtonStyled.vue' import StyledInput from '../../base/StyledInput.vue' import NewModal from '../../modal/NewModal.vue' diff --git a/packages/ui/src/components/servers/backups/BackupDeleteModal.vue b/packages/ui/src/components/servers/backups/BackupDeleteModal.vue index d00e857aad..860b81264a 100644 --- a/packages/ui/src/components/servers/backups/BackupDeleteModal.vue +++ b/packages/ui/src/components/servers/backups/BackupDeleteModal.vue @@ -60,22 +60,20 @@ @@ -86,11 +84,12 @@ import type { Archon } from '@modrinth/api-client' import { TrashIcon, XIcon } from '@modrinth/assets' import { computed, nextTick, ref } from 'vue' +import { Button } from '#ui/components/base/buttons' + import { defineMessages, useVIntl } from '../../../composables/i18n' import { useScrollIndicator } from '../../../composables/scroll-indicator' import { commonMessages } from '../../../utils' import Admonition from '../../base/Admonition.vue' -import ButtonStyled from '../../base/ButtonStyled.vue' import NewModal from '../../modal/NewModal.vue' import BackupItem from './BackupItem.vue' diff --git a/packages/ui/src/components/servers/backups/BackupItem.vue b/packages/ui/src/components/servers/backups/BackupItem.vue index 4d883227c7..dc5b4f7c01 100644 --- a/packages/ui/src/components/servers/backups/BackupItem.vue +++ b/packages/ui/src/components/servers/backups/BackupItem.vue @@ -13,13 +13,14 @@ import { } from '@modrinth/assets' import { computed, ref } from 'vue' +import type { OverflowMenuOption } from '#ui/components/base/buttons' +import { Button, TeleportOverflowMenu } from '#ui/components/base/buttons' + import { useFormatDateTime } from '../../../composables' import { defineMessages, useVIntl } from '../../../composables/i18n' import { commonMessages, truncatedTooltip } from '../../../utils' import AutoLink from '../../base/AutoLink.vue' import Avatar from '../../base/Avatar.vue' -import ButtonStyled from '../../base/ButtonStyled.vue' -import OverflowMenu, { type Option as OverflowOption } from '../../base/OverflowMenu.vue' const { formatMessage } = useVIntl() const formatDateTime = useFormatDateTime({ @@ -93,38 +94,42 @@ const itemBorderClass = computed(() => { return 'border-transparent' }) -const overflowMenuOptions = computed(() => { - const options: OverflowOption[] = [] +const overflowMenuOptions = computed(() => { + const options: OverflowMenuOption[] = [] if (props.showCopyIdAction) { options.push({ id: 'copy-id', + label: formatMessage(commonMessages.copyIdButton), action: () => copyId(), }) } if (options.length > 0) { - options.push({ divider: true }) + options.push({ type: 'divider' }) } options.push({ id: 'download', - action: () => emit('download'), - link: `https://${props.kyrosUrl}/modrinth/v0/backups/${props.backup.id}/download?auth=${props.jwt}`, + label: formatMessage(commonMessages.downloadButton), + type: 'link', + href: `https://${props.kyrosUrl}/modrinth/v0/backups/${props.backup.id}/download?auth=${props.jwt}`, disabled: !props.kyrosUrl || !props.jwt, }) options.push({ id: 'rename', + label: formatMessage(messages.rename), action: () => emit('rename'), disabled: props.writeDisabled, tooltip: props.writeDisabled ? props.writeDisabledTooltip : undefined, }) - options.push({ divider: true }) + options.push({ type: 'divider' }) options.push({ id: 'delete', - color: 'red', + label: formatMessage(commonMessages.deleteLabel), + tone: 'red', action: () => emit('delete'), disabled: props.writeDisabled, tooltip: props.writeDisabled ? props.writeDisabledTooltip : undefined, @@ -265,35 +270,37 @@ const creatorAvatarSrc = computed(() =>
- - - - - - - - - - - - + + + + + + + +
{{
diff --git a/packages/ui/src/components/servers/backups/BackupRenameModal.vue b/packages/ui/src/components/servers/backups/BackupRenameModal.vue
index 54d090990a..97c34d2a69 100644
--- a/packages/ui/src/components/servers/backups/BackupRenameModal.vue
+++ b/packages/ui/src/components/servers/backups/BackupRenameModal.vue
@@ -22,28 +22,26 @@
 		
@@ -55,6 +53,8 @@ import { IssuesIcon, SaveIcon, SpinnerIcon, XIcon } from '@modrinth/assets' import { useMutation, useQueryClient } from '@tanstack/vue-query' import { computed, nextTick, ref } from 'vue' +import { Button } from '#ui/components/base/buttons' + import { useVIntl } from '../../../composables/i18n' import { injectModrinthClient, @@ -62,7 +62,6 @@ import { injectNotificationManager, } from '../../../providers' import { commonMessages } from '../../../utils' -import ButtonStyled from '../../base/ButtonStyled.vue' import StyledInput from '../../base/StyledInput.vue' import NewModal from '../../modal/NewModal.vue' diff --git a/packages/ui/src/components/servers/backups/BackupRestoreModal.vue b/packages/ui/src/components/servers/backups/BackupRestoreModal.vue index 64dc1de2c6..016dccbe37 100644 --- a/packages/ui/src/components/servers/backups/BackupRestoreModal.vue +++ b/packages/ui/src/components/servers/backups/BackupRestoreModal.vue @@ -17,23 +17,21 @@ @@ -45,6 +43,8 @@ import { RotateCounterClockwiseIcon, SpinnerIcon, XIcon } from '@modrinth/assets import { useMutation, useQueryClient } from '@tanstack/vue-query' import { computed, ref } from 'vue' +import { Button } from '#ui/components/base/buttons' + import { useVIntl } from '../../../composables/i18n' import { injectModrinthClient, @@ -53,7 +53,6 @@ import { } from '../../../providers' import { commonMessages } from '../../../utils' import Admonition from '../../base/Admonition.vue' -import ButtonStyled from '../../base/ButtonStyled.vue' import NewModal from '../../modal/NewModal.vue' import BackupItem from './BackupItem.vue' diff --git a/packages/ui/src/components/servers/edit-server-icon/EditServerIcon.vue b/packages/ui/src/components/servers/edit-server-icon/EditServerIcon.vue index 936e415cca..30967a7440 100644 --- a/packages/ui/src/components/servers/edit-server-icon/EditServerIcon.vue +++ b/packages/ui/src/components/servers/edit-server-icon/EditServerIcon.vue @@ -2,19 +2,25 @@
Icon
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/ui/src/components/servers/icons/index.ts b/packages/ui/src/components/servers/icons/index.ts index 71498d858d..4e635fb3f2 100644 --- a/packages/ui/src/components/servers/icons/index.ts +++ b/packages/ui/src/components/servers/icons/index.ts @@ -1,2 +1 @@ -export { default as LoaderIcon } from './LoaderIcon.vue' export { default as ServerIcon } from './ServerIcon.vue' diff --git a/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue b/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue index 9478a89a9e..8ad5a4e626 100644 --- a/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue +++ b/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue @@ -2,7 +2,7 @@
- +
- - - +
- - - +
@@ -168,10 +174,11 @@ import dayjsDuration from 'dayjs/plugin/duration' import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { useRouter } from 'vue-router' +import { Button } from '#ui/components/base/buttons' + import { defineMessages, useVIntl } from '../../../composables/i18n' import { injectModrinthClient } from '../../../providers/api-client' import Avatar from '../../base/Avatar.vue' -import ButtonStyled from '../../base/ButtonStyled.vue' import CopyCode from '../../base/CopyCode.vue' import IntlFormatted from '../../base/IntlFormatted.vue' import type { ServerListingOwner } from '../access/types' diff --git a/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue b/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue index 52d47effe2..381aed3b28 100644 --- a/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue +++ b/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue @@ -1,60 +1,75 @@
@@ -153,7 +149,7 @@ import { } from '@modrinth/assets' import { AutoLink } from '@modrinth/ui' -import ButtonStyled from '#ui/components/base/ButtonStyled.vue' +import { Button } from '#ui/components/base/buttons' import { defineMessages, useVIntl } from '../../../composables/i18n' import imgAircraft from './grid-images/aircraft.png' diff --git a/packages/ui/src/components/sharing/invite-players-modal/index.vue b/packages/ui/src/components/sharing/invite-players-modal/index.vue index 038c7e8e8f..1188a1fcd9 100644 --- a/packages/ui/src/components/sharing/invite-players-modal/index.vue +++ b/packages/ui/src/components/sharing/invite-players-modal/index.vue @@ -54,17 +54,17 @@
- - - +
@@ -102,18 +102,17 @@
{{ inviteLinkHeading }}
- - - +

{{ linkExpiryDescription }}