diff --git a/apps/frontend/src/composables/featureFlags.ts b/apps/frontend/src/composables/featureFlags.ts index 7832eb99..bf17955c 100644 --- a/apps/frontend/src/composables/featureFlags.ts +++ b/apps/frontend/src/composables/featureFlags.ts @@ -31,6 +31,9 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({ projectBackground: false, searchBackground: false, advancedDebugInfo: false, + showProjectPageDownloadModalServersPromo: true, + showProjectPageCreateServersTooltip: true, + showProjectPageQuickServerButton: false, // advancedRendering: true, // externalLinksNewTab: true, // notUsingBlockers: false, diff --git a/apps/frontend/src/pages/[type]/[id].vue b/apps/frontend/src/pages/[type]/[id].vue index 7edde26f..4b572120 100644 --- a/apps/frontend/src/pages/[type]/[id].vue +++ b/apps/frontend/src/pages/[type]/[id].vue @@ -452,6 +452,16 @@ {{ formatCategory(currentPlatform) }}.
++ Modrinth Servers is the easiest way to play with your friends without hassle! +
++ Starting at $5 / month +
+Starting at $3/GB RAM
++ Starting at {{ formatPrice(locale, lowestPrice, selectedCurrency, true) }} / month +
@@ -622,20 +625,34 @@ import { VersionIcon, ServerIcon, } from "@modrinth/assets"; +import { computed } from "vue"; +import { monthsInInterval } from "@modrinth/ui/src/utils/billing.ts"; +import { formatPrice } from "@modrinth/utils"; +import { useVIntl } from "@vintl/vintl"; import { products } from "~/generated/state.json"; import { useServersFetch } from "~/composables/servers/servers-fetch.ts"; import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue"; import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue"; import OptionGroup from "~/components/ui/OptionGroup.vue"; +const { locale } = useVIntl(); + const billingPeriods = ref(["monthly", "quarterly"]); const billingPeriod = ref(billingPeriods.value.includes("quarterly") ? "quarterly" : "monthly"); -const pyroProducts = products.filter((p) => p.metadata.type === "pyro"); +const pyroProducts = products + .filter((p) => p.metadata.type === "pyro") + .sort((a, b) => a.metadata.ram - b.metadata.ram); const pyroPlanProducts = pyroProducts.filter( (p) => p.metadata.ram === 4096 || p.metadata.ram === 6144 || p.metadata.ram === 8192, ); -pyroPlanProducts.sort((a, b) => a.metadata.ram - b.metadata.ram); + +const lowestPrice = computed(() => { + const amount = pyroProducts[0]?.prices?.find( + (price) => price.currency_code === selectedCurrency.value, + )?.prices?.intervals?.[billingPeriod.value]; + return amount ? amount / monthsInInterval[billingPeriod.value] : undefined; +}); const title = "Modrinth Servers"; const description = @@ -799,6 +816,8 @@ async function fetchPaymentData() { } } +const selectedProjectId = ref(); + const route = useRoute(); const isAtCapacity = computed( () => isSmallAtCapacity.value && isMediumAtCapacity.value && isLargeAtCapacity.value, @@ -817,7 +836,12 @@ const scrollToFaq = () => { } }; -onMounted(scrollToFaq); +onMounted(() => { + scrollToFaq(); + if (route.query?.project) { + selectedProjectId.value = route.query?.project; + } +}); watch(() => route.hash, scrollToFaq); @@ -876,9 +900,9 @@ const selectProduct = async (product) => { await nextTick(); if (product === "custom") { - purchaseModal.value?.show(billingPeriod.value); + purchaseModal.value?.show(billingPeriod.value, undefined, selectedProjectId.value); } else { - purchaseModal.value?.show(billingPeriod.value, selectedProduct.value); + purchaseModal.value?.show(billingPeriod.value, selectedProduct.value, selectedProjectId.value); } }; diff --git a/apps/frontend/src/plugins/floating-vue.js b/apps/frontend/src/plugins/floating-vue.js index 8ea6c4c8..c32d2b66 100644 --- a/apps/frontend/src/plugins/floating-vue.js +++ b/apps/frontend/src/plugins/floating-vue.js @@ -10,6 +10,10 @@ export default defineNuxtPlugin((nuxtApp) => { instantMove: true, distance: 8, }, + "dismissable-prompt": { + $extend: "dropdown", + placement: "bottom-start", + }, }, }); }); diff --git a/packages/assets/icons/badge-check.svg b/packages/assets/icons/badge-check.svg new file mode 100644 index 00000000..ad45322e --- /dev/null +++ b/packages/assets/icons/badge-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/assets/icons/server-plus.svg b/packages/assets/icons/server-plus.svg new file mode 100644 index 00000000..b9bab1bd --- /dev/null +++ b/packages/assets/icons/server-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/assets/index.ts b/packages/assets/index.ts index df7df424..dbb8141b 100644 --- a/packages/assets/index.ts +++ b/packages/assets/index.ts @@ -44,6 +44,7 @@ import _AlignLeftIcon from './icons/align-left.svg?component' import _ArchiveIcon from './icons/archive.svg?component' import _ArrowBigUpDashIcon from './icons/arrow-big-up-dash.svg?component' import _AsteriskIcon from './icons/asterisk.svg?component' +import _BadgeCheckIcon from './icons/badge-check.svg?component' import _BanIcon from './icons/ban.svg?component' import _BellIcon from './icons/bell.svg?component' import _BellRingIcon from './icons/bell-ring.svg?component' @@ -163,6 +164,7 @@ import _ScanEyeIcon from './icons/scan-eye.svg?component' import _SearchIcon from './icons/search.svg?component' import _SendIcon from './icons/send.svg?component' import _ServerIcon from './icons/server.svg?component' +import _ServerPlusIcon from './icons/server-plus.svg?component' import _SettingsIcon from './icons/settings.svg?component' import _ShareIcon from './icons/share.svg?component' import _ShieldIcon from './icons/shield.svg?component' @@ -264,6 +266,7 @@ export const AlignLeftIcon = _AlignLeftIcon export const ArchiveIcon = _ArchiveIcon export const ArrowBigUpDashIcon = _ArrowBigUpDashIcon export const AsteriskIcon = _AsteriskIcon +export const BadgeCheckIcon = _BadgeCheckIcon export const BanIcon = _BanIcon export const BellIcon = _BellIcon export const BellRingIcon = _BellRingIcon @@ -383,6 +386,7 @@ export const ScanEyeIcon = _ScanEyeIcon export const SearchIcon = _SearchIcon export const SendIcon = _SendIcon export const ServerIcon = _ServerIcon +export const ServerPlusIcon = _ServerPlusIcon export const SettingsIcon = _SettingsIcon export const ShareIcon = _ShareIcon export const ShieldIcon = _ShieldIcon diff --git a/packages/assets/styles/classes.scss b/packages/assets/styles/classes.scss index 9b22f853..6082cb09 100644 --- a/packages/assets/styles/classes.scss +++ b/packages/assets/styles/classes.scss @@ -822,10 +822,69 @@ a, // TOOLTIPS +.v-popper--theme-dropdown, +.v-popper--theme-dropdown.v-popper--theme-ribbit-popout { + .v-popper__inner { + border: 1px solid var(--color-button-bg) !important; + padding: var(--gap-sm) !important; + width: fit-content !important; + border-radius: var(--radius-md) !important; + background-color: var(--color-raised-bg) !important; + box-shadow: var(--shadow-floating) !important; + } + + .v-popper__arrow-outer { + border-color: var(--color-button-bg) !important; + } + + .v-popper__arrow-inner { + border-color: var(--color-raised-bg) !important; + } +} + +.v-popper__popper[data-popper-placement='bottom-end'] .v-popper__wrapper { + transform-origin: top right; +} + +.v-popper__popper[data-popper-placement='top-end'] .v-popper__wrapper { + transform-origin: bottom right; +} + +.v-popper__popper[data-popper-placement='bottom-start'] .v-popper__wrapper { + transform-origin: top left; +} + +.v-popper__popper[data-popper-placement='top-start'] .v-popper__wrapper { + transform-origin: bottom left; +} + +.v-popper__popper.v-popper__popper--show-from .v-popper__wrapper { + transform: scale(0.85); + opacity: 0; +} + +.v-popper__popper.v-popper__popper--show-to .v-popper__wrapper { + transform: scale(1); + opacity: 1; + transition: + transform 0.125s ease-in-out, + opacity 0.125s ease-in-out; +} + +.v-popper__popper.v-popper__popper--hide-from .v-popper__wrapper { + transform: none; + opacity: 1; + transition: transform 0.0625s; +} + +.v-popper__popper.v-popper__popper--hide-to .v-popper__wrapper { + //transform: scale(.9); +} + .v-popper--theme-tooltip { .v-popper__inner { background: var(--color-tooltip-bg) !important; - color: var(--color-tooltip-text) !important; + color: initial !important; padding: 0.5rem 0.5rem !important; border-radius: var(--radius-sm) !important; filter: drop-shadow(5px 5px 0.8rem rgba(0, 0, 0, 0.35)); @@ -840,6 +899,30 @@ a, } } +.v-popper--theme-dismissable-prompt { + z-index: 10; + + .v-popper__inner { + background: var(--color-raised-bg) !important; + border: 1px solid var(--color-button-border); + color: var(--color-tooltip-text) !important; + padding: 0.75rem 1rem !important; + border-radius: 0.75rem !important; + filter: drop-shadow(5px 5px 0.8rem rgba(0, 0, 0, 0.35)); + font-size: 0.9rem; + font-weight: bold; + line-height: 1; + } + + .v-popper__arrow-outer { + border-color: var(--color-button-border); + } + + .v-popper__arrow-inner { + border-color: var(--color-raised-bg); + } +} + // MARKDOWN .markdown-body { @@ -1205,65 +1288,6 @@ select { border-top-right-radius: var(--radius-md) !important; } -.v-popper--theme-dropdown, -.v-popper--theme-dropdown.v-popper--theme-ribbit-popout { - .v-popper__inner { - border: 1px solid var(--color-button-bg) !important; - padding: var(--gap-sm) !important; - width: fit-content !important; - border-radius: var(--radius-md) !important; - background-color: var(--color-raised-bg) !important; - box-shadow: var(--shadow-floating) !important; - } - - .v-popper__arrow-outer { - border-color: var(--color-button-bg) !important; - } - - .v-popper__arrow-inner { - border-color: var(--color-raised-bg) !important; - } -} - -.v-popper__popper[data-popper-placement='bottom-end'] .v-popper__wrapper { - transform-origin: top right; -} - -.v-popper__popper[data-popper-placement='top-end'] .v-popper__wrapper { - transform-origin: bottom right; -} - -.v-popper__popper[data-popper-placement='bottom-start'] .v-popper__wrapper { - transform-origin: top left; -} - -.v-popper__popper[data-popper-placement='top-start'] .v-popper__wrapper { - transform-origin: bottom left; -} - -.v-popper__popper.v-popper__popper--show-from .v-popper__wrapper { - transform: scale(0.85); - opacity: 0; -} - -.v-popper__popper.v-popper__popper--show-to .v-popper__wrapper { - transform: scale(1); - opacity: 1; - transition: - transform 0.125s ease-in-out, - opacity 0.125s ease-in-out; -} - -.v-popper__popper.v-popper__popper--hide-from .v-popper__wrapper { - transform: none; - opacity: 1; - transition: transform 0.0625s; -} - -.v-popper__popper.v-popper__popper--hide-to .v-popper__wrapper { - //transform: scale(.9); -} - .preview-radio { width: 100% !important; border-radius: var(--radius-md); diff --git a/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue b/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue index 74e618fe..c7b9ab3a 100644 --- a/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue +++ b/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue @@ -59,6 +59,7 @@ const selectedPlan = ref+ {{ formatPrice(locale, selectedPrice, currency, true) }} / month, billed {{ interval }} +
+