You've already forked AstralRinth
forked from didirus/AstralRinth
Add quick server button, dynamic price preview for custom server modal (#3815)
* Add quick server creation button, and dynamic pricing to custom server selection * Remove test in compatibility card * Lint + remove duplicate file * Adjust z-index of popup * $6 -> $5 * Dismiss prompt if the button is clicked * Make "Create a server" disabled for now * Use existing loaders type
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -452,6 +452,16 @@
|
||||
{{ formatCategory(currentPlatform) }}.
|
||||
</p>
|
||||
</AutomaticAccordion>
|
||||
<ServersPromo
|
||||
v-if="flags.showProjectPageDownloadModalServersPromo"
|
||||
:link="`/servers#plan`"
|
||||
@close="
|
||||
() => {
|
||||
flags.showProjectPageDownloadModalServersPromo = false;
|
||||
saveFeatureFlags();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</NewModal>
|
||||
@@ -495,6 +505,64 @@
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<Tooltip
|
||||
v-if="canCreateServerFrom && flags.showProjectPageQuickServerButton"
|
||||
theme="dismissable-prompt"
|
||||
:triggers="[]"
|
||||
:shown="flags.showProjectPageCreateServersTooltip"
|
||||
:auto-hide="false"
|
||||
placement="bottom-start"
|
||||
>
|
||||
<ButtonStyled size="large" circular>
|
||||
<nuxt-link
|
||||
v-tooltip="'Create a server'"
|
||||
:to="`/servers?project=${project.id}#plan`"
|
||||
@click="
|
||||
() => {
|
||||
flags.showProjectPageCreateServersTooltip = false;
|
||||
saveFeatureFlags();
|
||||
}
|
||||
"
|
||||
>
|
||||
<ServerPlusIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<template #popper>
|
||||
<div class="experimental-styles-within flex max-w-60 flex-col gap-1">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3 class="m-0 flex items-center gap-2 text-base font-bold text-contrast">
|
||||
Create a server
|
||||
<TagItem
|
||||
:style="{
|
||||
'--_color': 'var(--color-brand)',
|
||||
'--_bg-color': 'var(--color-brand-highlight)',
|
||||
}"
|
||||
>New</TagItem
|
||||
>
|
||||
</h3>
|
||||
<ButtonStyled size="small" circular>
|
||||
<button
|
||||
v-tooltip="`Don't show again`"
|
||||
@click="
|
||||
() => {
|
||||
flags.showProjectPageCreateServersTooltip = false;
|
||||
saveFeatureFlags();
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="m-0 text-wrap text-sm font-medium leading-tight text-secondary">
|
||||
Modrinth Servers is the easiest way to play with your friends without hassle!
|
||||
</p>
|
||||
<p class="m-0 text-wrap text-sm font-bold text-primary">
|
||||
Starting at $5<span class="text-xs"> / month</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
<ClientOnly>
|
||||
<ButtonStyled
|
||||
size="large"
|
||||
@@ -850,12 +918,14 @@ import {
|
||||
ReportIcon,
|
||||
ScaleIcon,
|
||||
SearchIcon,
|
||||
ServerPlusIcon,
|
||||
SettingsIcon,
|
||||
TagsIcon,
|
||||
UsersIcon,
|
||||
VersionIcon,
|
||||
WrenchIcon,
|
||||
ModrinthIcon,
|
||||
XIcon,
|
||||
} from "@modrinth/assets";
|
||||
import {
|
||||
Avatar,
|
||||
@@ -872,6 +942,8 @@ import {
|
||||
ProjectSidebarLinks,
|
||||
ProjectStatusBadge,
|
||||
ScrollablePanel,
|
||||
TagItem,
|
||||
ServersPromo,
|
||||
useRelativeTime,
|
||||
} from "@modrinth/ui";
|
||||
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
|
||||
@@ -885,6 +957,7 @@ import {
|
||||
} from "@modrinth/utils";
|
||||
import { navigateTo } from "#app";
|
||||
import dayjs from "dayjs";
|
||||
import { Tooltip } from "floating-vue";
|
||||
import Accordion from "~/components/ui/Accordion.vue";
|
||||
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
||||
import AutomaticAccordion from "~/components/ui/AutomaticAccordion.vue";
|
||||
@@ -898,6 +971,7 @@ import NavTabs from "~/components/ui/NavTabs.vue";
|
||||
import ProjectMemberHeader from "~/components/ui/ProjectMemberHeader.vue";
|
||||
import { userCollectProject } from "~/composables/user.js";
|
||||
import { reportProject } from "~/utils/report-helpers.ts";
|
||||
import { saveFeatureFlags } from "~/composables/featureFlags.ts";
|
||||
|
||||
const data = useNuxtApp();
|
||||
const route = useNativeRoute();
|
||||
@@ -1311,6 +1385,10 @@ const description = computed(
|
||||
} by ${members.value.find((x) => x.is_owner)?.user?.username || "a Creator"} on Modrinth`,
|
||||
);
|
||||
|
||||
const canCreateServerFrom = computed(() => {
|
||||
return project.value.project_type === "modpack" && project.value.server_side !== "unsupported";
|
||||
});
|
||||
|
||||
if (!route.name.startsWith("type-id-settings")) {
|
||||
useSeoMeta({
|
||||
title: () => title.value,
|
||||
@@ -1679,4 +1757,33 @@ const navLinks = computed(() => {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.servers-popup {
|
||||
box-shadow:
|
||||
0 0 12px 1px rgba(0, 175, 92, 0.6),
|
||||
var(--shadow-floating);
|
||||
|
||||
&::before {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid var(--color-button-bg);
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 17px;
|
||||
}
|
||||
&::after {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid var(--color-raised-bg);
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -500,6 +500,7 @@
|
||||
|
||||
<section
|
||||
id="plan"
|
||||
pyro-hash="plan"
|
||||
class="relative mt-24 flex flex-col bg-[radial-gradient(65%_50%_at_50%_-10%,var(--color-brand-highlight)_0%,var(--color-accent-contrast)_100%)] px-3 pt-24 md:mt-48 md:pt-48"
|
||||
>
|
||||
<div class="faded-brand-line absolute left-0 top-0 h-[1px] w-full"></div>
|
||||
@@ -603,7 +604,9 @@
|
||||
<RightArrowIcon class="shrink-0" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<p class="m-0 text-sm">Starting at $3/GB RAM</p>
|
||||
<p v-if="lowestPrice" class="m-0 text-sm">
|
||||
Starting at {{ formatPrice(locale, lowestPrice, selectedCurrency, true) }} / month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
instantMove: true,
|
||||
distance: 8,
|
||||
},
|
||||
"dismissable-prompt": {
|
||||
$extend: "dropdown",
|
||||
placement: "bottom-start",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user