You've already forked AstralRinth
forked from didirus/AstralRinth
Pyro Integration (#2503)
* fix Signed-off-by: Evan Song <theevansong@gmail.com> * fix Signed-off-by: Evan Song <theevansong@gmail.com> * refactor(fileitem): optimize Signed-off-by: Evan Song <theevansong@gmail.com> * chore(fileitem): fixed width timestamp Signed-off-by: Evan Song <theevansong@gmail.com> * fix(fileitem): allow editing json5/jsonc Signed-off-by: Evan Song <theevansong@gmail.com> * feat: motd pt 1, auto backups scaffolding, editing navbar changes * feat: fancy sidebar animations * fix: files * fix: files pt2 * fix: faulty name validation disallowing spaces in file names Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: fileitem props Signed-off-by: Evan Song <theevansong@gmail.com> * fix: upload files not refreshing files list Signed-off-by: Evan Song <theevansong@gmail.com> * fix(imgviewer): handle invalid/empty images Signed-off-by: Evan Song <theevansong@gmail.com> * fix: return of the sticky files header Signed-off-by: Evan Song <theevansong@gmail.com> * chore: prevent servericon from shrinking Signed-off-by: Evan Song <theevansong@gmail.com> * fix: wtf were we thinking with this anyway Signed-off-by: Evan Song <theevansong@gmail.com> * fix: further mobile optimization Signed-off-by: Evan Song <theevansong@gmail.com> * chore: propagate margin Signed-off-by: Evan Song <theevansong@gmail.com> * chore: truncation fixes Signed-off-by: Evan Song <theevansong@gmail.com> * fix: track navbar with sentinel Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * fix(files): a11y Signed-off-by: Evan Song <theevansong@gmail.com> * chore: improve inspector styles Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * feat: console preformance improvements, decrease blur * feat(mobile): new server header * fix: linting * fix: useless z indeces Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust file filter names Signed-off-by: Evan Song <theevansong@gmail.com> * feat(files): true breadcrumbs Signed-off-by: Evan Song <theevansong@gmail.com> * fix(marketing): make custom responsive * fix(marketing): mobile file manager card * feat: trackable navtabs Signed-off-by: Evan Song <theevansong@gmail.com> * fix: oh no Signed-off-by: Evan Song <theevansong@gmail.com> * fix: smartly truncate Signed-off-by: Evan Song <theevansong@gmail.com> * fix(terminal): z-indexes * fix: autofocus more inputs Signed-off-by: Evan Song <theevansong@gmail.com> * fix: color Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust copy Signed-off-by: Evan Song <theevansong@gmail.com> * chore: backup modal usability improvements Signed-off-by: Evan Song <theevansong@gmail.com> * fix: padding Signed-off-by: Evan Song <theevansong@gmail.com> * chore: title Signed-off-by: Evan Song <theevansong@gmail.com> * fix(content): update banner mobile support * fix: server listing icons Signed-off-by: Evan Song <theevansong@gmail.com> * fix: ignore clicks in server listing for labels Signed-off-by: Evan Song <theevansong@gmail.com> * feat(mobile): backup card * fix(backups): make plural conditional * fix: debounce file item selectitem Signed-off-by: Evan Song <theevansong@gmail.com> * fix: lint Signed-off-by: Evan Song <theevansong@gmail.com> * stuff Signed-off-by: Evan Song <theevansong@gmail.com> * fix: temp sidebar fix until i can be smart * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * chore: explictly set button type in file modals Signed-off-by: Evan Song <theevansong@gmail.com> * fix: properly sort backups Signed-off-by: Evan Song <theevansong@gmail.com> * feat: add getautobackup method to pyroservers Signed-off-by: Evan Song <theevansong@gmail.com> * choer: update autobackup params Signed-off-by: Evan Song <theevansong@gmail.com> * chore: update autobackup methods (REALLY GUYS) Signed-off-by: Evan Song <theevansong@gmail.com> * feat: implement autobackups Signed-off-by: Evan Song <theevansong@gmail.com> * feat: implement backup-while-running preference Signed-off-by: Evan Song <theevansong@gmail.com> * feat: make server labels a component * feat: implement 'All details' modal * fix(mobile): server manage page * feat(files): mobile compatible * fix(info labels): wrap * chore(inspector): clean Signed-off-by: Evan Song <theevansong@gmail.com> * fix(backup settings): swap + and - * fix(manage): new -> plans instead of modal * feat: more small mobile fixes * fix(auto backup modal): manual input validation * fix(file browse navbar): home margin * feat(purchase modal): mobile support * fix(marketing): faded line alignments * feat: add servers to mobile nav * feat(network): dns record fixes * feat: make all settings work on mobile * fix(loader settings): modpack mobile * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * feat(marketing): add 'Manage your servers' button * fix(marketing): only check servers if logged in * fix(network): allocation edit & delete button * fix(backups): use UiServersTeleportOverflowMenu * chore: linting * chore: but here comes the sentence case Signed-off-by: Evan Song <theevansong@gmail.com> * feat(marketing): make buttons consistent * lint Signed-off-by: Evan Song <theevansong@gmail.com> * fix(loader): prevent multiline version names in dropdown Signed-off-by: Evan Song <theevansong@gmail.com> * lint Signed-off-by: Evan Song <theevansong@gmail.com> * fix: copy Signed-off-by: Evan Song <theevansong@gmail.com> * fix: sentence case Signed-off-by: Evan Song <theevansong@gmail.com> * fix: linting * chore: rename dumbass preference key Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: rewrite power action buttons Signed-off-by: Evan Song <theevansong@gmail.com> * fix: robust download logic Signed-off-by: Evan Song <theevansong@gmail.com> * fix(loader mobile): modpack dropdown width * fix: sentence case * fix(save & 'working on it'): look good on mobile * fix(TeleportDropdown): width * fix(inspecting error): mobile * fix: show action button dropdown when installing * fix(navtabs): temp fix for mobile scrolling issue * fix(install error): mobile compatible * chore: just remove tracking Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * fix: cleanup * fix: broken svg clr in checkbox when using experimental styles Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust vanilla icon Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust loader props Signed-off-by: Evan Song <theevansong@gmail.com> * revert changes to serversidebar Signed-off-by: Evan Song <theevansong@gmail.com> * fix: server properties flicker Signed-off-by: Evan Song <theevansong@gmail.com> * fix(backups): plural * fix: cases where the telepoverflow would clash with viewport edge Signed-off-by: Evan Song <theevansong@gmail.com> * feat(backups): auto-backups label * fix(network): titlecase * feat(fileitem): new rename icon * fix(properties): wiki proper noun * fix: disable motd for the time being * chore: adjust wording for power conifmration Signed-off-by: Evan Song <theevansong@gmail.com> * feat: "external" to billing Signed-off-by: Evan Song <theevansong@gmail.com> * fix: icon Signed-off-by: Evan Song <theevansong@gmail.com> * fix: add EULA checkbox * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * me and bro deciding which case rules to enforce Signed-off-by: Evan Song <theevansong@gmail.com> * feat(sftp): copy address & username, launch tooltip * feat(files): better move * chore: attempt to mitigate excessive stack depth type Signed-off-by: Evan Song <theevansong@gmail.com> * fix(loader): prevent versions 1.2.4 and below * feat(dns table): placeholder improvements * feat(pyroServer): error handling * fix: intrinsic size on loader icon Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust wording Signed-off-by: Evan Song <theevansong@gmail.com> * fix: sentence case Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust wording Signed-off-by: Evan Song <theevansong@gmail.com> * fix: types Signed-off-by: Evan Song <theevansong@gmail.com> * fix: "implemented" key in preference Signed-off-by: Evan Song <theevansong@gmail.com> * feat(connection lost): redesign * feat(connection error): make icon orange * fix: cleanup * chore(connection lost): redesign pt 2 Signed-off-by: Evan Song <theevansong@gmail.com> * fix: OOOOHHH MY GOD Signed-off-by: Evan Song <theevansong@gmail.com> * feat: implement capacity api on marketing Signed-off-by: Evan Song <theevansong@gmail.com> * chore: update createdat backup type Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: all of backups Signed-off-by: Evan Song <theevansong@gmail.com> * chore: update backup types Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: backups pt 2 Signed-off-by: Evan Song <theevansong@gmail.com> * fix: comically small icons Signed-off-by: Evan Song <theevansong@gmail.com> * chore: align designs Signed-off-by: Evan Song <theevansong@gmail.com> * chore: hide ram graph if ram as bytes enabled Signed-off-by: Evan Song <theevansong@gmail.com> * base add content page * Fix conflict * feat(content): mobile-compatible header, sticky * fix(marketing): md instead of sm for custom * fix: compiler macro warning Signed-off-by: Evan Song <theevansong@gmail.com> * again Signed-off-by: Evan Song <theevansong@gmail.com> * fix: loader type error Signed-off-by: Evan Song <theevansong@gmail.com> * fix: default uptime seconds prop Signed-off-by: Evan Song <theevansong@gmail.com> * fix: hydration errors on server listing Signed-off-by: Evan Song <theevansong@gmail.com> * feat: move custom URL to general Signed-off-by: Evan Song <theevansong@gmail.com> * feat: indiviudally checkj capacities Signed-off-by: Evan Song <theevansong@gmail.com> * fix: falsey Signed-off-by: Evan Song <theevansong@gmail.com> * fix: missing prop Signed-off-by: Evan Song <theevansong@gmail.com> * chore: Derive On That Thang Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust gap Signed-off-by: Evan Song <theevansong@gmail.com> * fix: add default name for backups * fix: the backup number should PROBABLY be computed lol * fix(backups): truncate text, mobile fixes * fix(loader): modpack mobile fix * feat(plans): add vcpus * fix(backup modal): blank by default, maxlength * fix(subdomain): separate length & valid chars * feat: mrpack installs functionality (untested), forbidden handling, backups grammar * feat(content): make responsive on mobile * fix: disable plan buttons separately * fix(backup modal): update name max length * fix(purchase): wrapping on eula, eula link * fix: move skeleton * fix(server mobile header): truncation * fix(server header): proper alignment * Finish content page fixes * fix: who up rinthing Signed-off-by: Evan Song <theevansong@gmail.com> * wip Signed-off-by: Evan Song <theevansong@gmail.com> * fix(staging & email banner): z-index * feat: make eula tickbox more visible * fix: move "powered by pyro" below buttons on hero * fix: oops sorry ellie, also updated the main screenshot * feat: update content screenshot * fix: content page card should hide image on lg * feat: hide total storage for now * fix: terminal card now uses terminal icon * fix(marketing): make medium plan card border solid * feat: modloader card, move pyro BACK below buttons, beta release pill * fix: spinning logo should be behind hero * feat: surgically remove the hero's massive forehead * feat(marketing): mobile UI screenshot * fix(hero): z-index goes over mobile nav * fix: consistent borders, files breakpoints * chore: update turbo * chore: adjust hero sizing Signed-off-by: Evan Song <theevansong@gmail.com> * chore: mention region restrictions * chore: double check if we are at capcity Signed-off-by: Evan Song <theevansong@gmail.com> * fix: measure twice cut once Signed-off-by: Evan Song <theevansong@gmail.com> * chore: bro cut twice and measured once 💀 Signed-off-by: Evan Song <theevansong@gmail.com> * fix(marketing): login first * fix: out of capacity text when logged out * fix(slider): reset some values for frontend * feat: wip hero section Signed-off-by: Evan Song <theevansong@gmail.com> * New navigation to support the new products (#2879) * Nav * oops extra file * feat: mrpack uploading with existing modpack, fix: choose modpack duplicate * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * feat: update features section Signed-off-by: Evan Song <theevansong@gmail.com> * Nav adjustments * fix: server manager empty state clashing with loading state Signed-off-by: Evan Song <theevansong@gmail.com> * chore: query param hard Signed-off-by: Evan Song <theevansong@gmail.com> * fix: do not count uptime if crashed Signed-off-by: Evan Song <theevansong@gmail.com> * fix: grammar Signed-off-by: Evan Song <theevansong@gmail.com> * hide hero img on lg breakpoints * Make plugins a plug * chore: prep for buffered text selection terminal Signed-off-by: Evan Song <theevansong@gmail.com> * fix: marketing responsive stuff, n fixes * fix hoverable prop * fix: edit mod spacing * fix: type error for display name in dropdown Signed-off-by: Evan Song <theevansong@gmail.com> * feat: custom plans * fix: no more console.log * fix: properly linked prop label Signed-off-by: Evan Song <theevansong@gmail.com> * fix(install hero mobile): padding * fix: prevent x overflow on servers page Signed-off-by: Evan Song <theevansong@gmail.com> * fix lint oh ym fucking god yal Signed-off-by: Evan Song <theevansong@gmail.com> * Migrate modpack install to search * fix(custom plan): warning icon variable * fix: loading probally and modal loader things * fix(marketing): login icon colours * fix(marketing): responsiveness * fix(marketing): responsiveness v2 * fix: sync button for icon tm * fix(marketing): responsiveness v3 * fix: hero image Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * Remove prod override --------- Signed-off-by: Evan Song <theevansong@gmail.com> Co-authored-by: Evan Song <theevansong@gmail.com> Co-authored-by: TheWander02 <48934424+thewander02@users.noreply.github.com> Co-authored-by: he3als <65787561+he3als@users.noreply.github.com> Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com> Co-authored-by: Lio <git@lio.cat> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: not-nullptr <needhelpwithrift@gmail.com> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com> Co-authored-by: Prospector <prospectordev@gmail.com> Co-authored-by: sticks <tanner@teamhydra.dev>
This commit is contained in:
@@ -6,9 +6,11 @@ const props = withDefaults(
|
||||
color?: 'standard' | 'brand' | 'red' | 'orange' | 'green' | 'blue' | 'purple'
|
||||
size?: 'standard' | 'large'
|
||||
circular?: boolean
|
||||
type?: 'standard' | 'outlined' | 'transparent'
|
||||
type?: 'standard' | 'outlined' | 'transparent' | 'highlight'
|
||||
colorFill?: 'auto' | 'background' | 'text' | 'none'
|
||||
hoverColorFill?: 'auto' | 'background' | 'text' | 'none'
|
||||
highlightedStyle?: 'main-nav-primary' | 'main-nav-secondary'
|
||||
highlighted?: boolean
|
||||
}>(),
|
||||
{
|
||||
color: 'standard',
|
||||
@@ -17,23 +19,25 @@ const props = withDefaults(
|
||||
type: 'standard',
|
||||
colorFill: 'auto',
|
||||
hoverColorFill: 'auto',
|
||||
highlightedStyle: 'main-nav-primary',
|
||||
highlighted: false,
|
||||
},
|
||||
)
|
||||
|
||||
const colorVar = computed(() => {
|
||||
switch (props.color) {
|
||||
case 'brand':
|
||||
return 'var(--color-brand)'
|
||||
return props.type === 'highlight' ? 'var(--color-brand-highlight)' : 'var(--color-brand)'
|
||||
case 'red':
|
||||
return 'var(--color-red)'
|
||||
return props.type === 'highlight' ? 'var(--color-red-highlight)' : 'var(--color-red)'
|
||||
case 'orange':
|
||||
return 'var(--color-orange)'
|
||||
return props.type === 'highlight' ? 'var(--color-orange-highlight)' : 'var(--color-orange)'
|
||||
case 'green':
|
||||
return 'var(--color-green)'
|
||||
return props.type === 'highlight' ? 'var(--color-green-highlight)' : 'var(--color-green)'
|
||||
case 'blue':
|
||||
return 'var(--color-blue)'
|
||||
return props.type === 'highlight' ? 'var(--color-blue-highlight)' : 'var(--color-blue)'
|
||||
case 'purple':
|
||||
return 'var(--color-purple)'
|
||||
return props.type === 'highlight' ? 'var(--color-purple-highlight)' : 'var(--color-purple)'
|
||||
case 'standard':
|
||||
default:
|
||||
return null
|
||||
@@ -108,7 +112,11 @@ function setColorFill(
|
||||
if (colorVar.value) {
|
||||
if (fill === 'background') {
|
||||
colors.bg = colorVar.value
|
||||
colors.text = 'var(--color-accent-contrast)'
|
||||
if (props.type === 'highlight') {
|
||||
colors.text = 'var(--color-contrast)'
|
||||
} else {
|
||||
colors.text = 'var(--color-accent-contrast)'
|
||||
}
|
||||
} else if (fill === 'text') {
|
||||
colors.text = colorVar.value
|
||||
}
|
||||
@@ -117,6 +125,22 @@ function setColorFill(
|
||||
}
|
||||
|
||||
const colorVariables = computed(() => {
|
||||
if (props.highlighted) {
|
||||
let colors = {
|
||||
bg:
|
||||
props.highlightedStyle === 'main-nav-primary'
|
||||
? 'var(--color-brand-highlight)'
|
||||
: 'var(--color-button-bg)',
|
||||
text: 'var(--color-contrast)',
|
||||
icon:
|
||||
props.highlightedStyle === 'main-nav-primary'
|
||||
? 'var(--color-brand)'
|
||||
: 'var(--color-contrast)',
|
||||
}
|
||||
let hoverColors = JSON.parse(JSON.stringify(colors))
|
||||
return `--_bg: ${colors.bg}; --_text: ${colors.text}; --_icon: ${colors.icon}; --_hover-bg: ${hoverColors.bg}; --_hover-text: ${hoverColors.text}; --_hover-icon: ${hoverColors.icon};`
|
||||
}
|
||||
|
||||
let colors = {
|
||||
bg: 'var(--color-button-bg)',
|
||||
text: 'var(--color-base)',
|
||||
@@ -176,6 +200,10 @@ const colorVariables = computed(() => {
|
||||
background-color 0.25s ease-in-out,
|
||||
color 0.25s ease-in-out;
|
||||
|
||||
svg:first-child {
|
||||
color: var(--_icon, var(--_text));
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&[disabled='true'],
|
||||
&.disabled,
|
||||
|
||||
@@ -104,19 +104,30 @@ defineExpose({
|
||||
})
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (!dropdown.value) return
|
||||
|
||||
const isContextMenuClick = event.button === 2 || event.which === 3
|
||||
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
||||
if (
|
||||
dropdown.value.$el !== event.target &&
|
||||
!elements.includes(dropdown.value.$el) &&
|
||||
!dropdown.value.contains(event.target)
|
||||
(dropdown.value !== event.target &&
|
||||
!elements.includes(dropdown.value) &&
|
||||
!dropdown.value.contains(event.target)) ||
|
||||
isContextMenuClick
|
||||
) {
|
||||
dropdownVisible.value = false
|
||||
emit('close')
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener('mouseup', handleClickOutside)
|
||||
window.addEventListener('resize', updateDirection)
|
||||
window.addEventListener('scroll', updateDirection)
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
@@ -125,16 +136,11 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener('mouseup', handleClickOutside)
|
||||
window.removeEventListener('resize', updateDirection)
|
||||
window.removeEventListener('scroll', updateDirection)
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === 'Escape') {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -140,6 +140,9 @@ const onInput = (value: string) => {
|
||||
border-radius: var(--radius-sm);
|
||||
height: 0.25rem;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
min-height: 0px;
|
||||
box-shadow: none;
|
||||
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
|
||||
@@ -3,23 +3,45 @@
|
||||
<template #title>
|
||||
<span class="text-contrast text-xl font-extrabold">
|
||||
<template v-if="product.metadata.type === 'midas'">Subscribe to Modrinth Plus!</template>
|
||||
<template v-else-if="product.metadata.type === 'pyro'"
|
||||
>Subscribe to Modrinth Servers!</template
|
||||
>
|
||||
<template v-else>Purchase product</template>
|
||||
</span>
|
||||
</template>
|
||||
<div class="flex items-center gap-1 pb-4">
|
||||
<template v-if="product.metadata.type === 'pyro' && !projectId">
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 0,
|
||||
'font-bold': purchaseModalStep === 0,
|
||||
}"
|
||||
>
|
||||
Configure
|
||||
<span class="hidden sm:inline">server</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
</template>
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 0,
|
||||
'font-bold': purchaseModalStep === 0,
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 1 : 0),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 1 : 0),
|
||||
}"
|
||||
>
|
||||
Select plan
|
||||
{{ product.metadata.type === 'pyro' ? 'Billing' : 'Plan' }}
|
||||
<span class="hidden sm:inline">{{
|
||||
product.metadata.type === 'pyro' ? 'interval' : 'selection'
|
||||
}}</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 1,
|
||||
'font-bold': purchaseModalStep === 1,
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 2 : 1),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 2 : 1),
|
||||
}"
|
||||
>
|
||||
Payment
|
||||
@@ -27,14 +49,115 @@
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 2,
|
||||
'font-bold': purchaseModalStep === 2,
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 3 : 2),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 3 : 2),
|
||||
}"
|
||||
>
|
||||
Review
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="purchaseModalStep === 0" class="md:w-[600px]">
|
||||
<div
|
||||
v-if="product.metadata.type === 'pyro' && !projectId && purchaseModalStep === 0"
|
||||
class="md:w-[600px] flex flex-col gap-4"
|
||||
>
|
||||
<div>
|
||||
<p class="my-2 text-lg font-bold">Configure your server</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
<input v-model="serverName" placeholder="Server name" class="input" maxlength="48" />
|
||||
<!-- <DropdownSelect
|
||||
v-model="serverLoader"
|
||||
v-tooltip="'Select the mod loader for your server'"
|
||||
name="server-loader"
|
||||
:options="['Vanilla', 'Fabric', 'Forge']"
|
||||
placeholder="Select mod loader..."
|
||||
/> -->
|
||||
<div class="grid lg:grid-cols-5 grid-cols-3 gap-4">
|
||||
<button
|
||||
v-for="loader in ['Vanilla', 'Fabric', 'Forge', 'Quilt', 'NeoForge']"
|
||||
:key="loader"
|
||||
class="!h-24 btn flex !flex-col !items-center !justify-between !pt-4 !pb-3 !w-full"
|
||||
:style="{
|
||||
filter: serverLoader === loader ? 'brightness(1.5)' : '',
|
||||
}"
|
||||
@click="serverLoader = loader"
|
||||
>
|
||||
<UiServersIconsLoaderIcon :loader="loader" class="!h-12 !w-12" />
|
||||
<p class="text-lg font-bold m-0">{{ loader }}</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<InfoIcon class="hidden sm:block" />
|
||||
<span class="text-sm text-secondary">
|
||||
You can change these settings later in your server options.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customServer">
|
||||
<div class="flex gap-2 items-center">
|
||||
<p class="my-2 text-lg font-bold">Configure your RAM</p>
|
||||
<IssuesIcon
|
||||
v-if="customServerConfig.ramInGb < 4"
|
||||
v-tooltip="'This might not be enough resources for your Minecraft server.'"
|
||||
class="h-6 w-6 text-orange"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex w-full gap-2 items-center">
|
||||
<Slider
|
||||
v-model="customServerConfig.ramInGb"
|
||||
class="fix-slider"
|
||||
:min="2"
|
||||
:max="12"
|
||||
:step="2"
|
||||
unit="GB"
|
||||
/>
|
||||
<div class="font-semibold text-nowrap"></div>
|
||||
</div>
|
||||
<div v-if="customMatchingProduct" class="flex sm:flex-row flex-col gap-4 w-full">
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<div class="font-semibold">vCPUs</div>
|
||||
<input v-model="mutatedProduct.metadata.cpu" disabled class="input" />
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<div class="font-semibold">Storage</div>
|
||||
<input v-model="customServerConfig.storageGbFormatted" disabled class="input" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex justify-between rounded-2xl border-2 border-solid border-blue bg-bg-blue p-4 font-semibold text-contrast"
|
||||
>
|
||||
<div class="flex w-full justify-between gap-2">
|
||||
<div class="flex flex-row gap-4">
|
||||
<InfoIcon class="hidden flex-none h-8 w-8 text-blue sm:block" />
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="font-semibold">We can't seem to find your selected plan</div>
|
||||
<div class="font-normal">
|
||||
We are currently unable to find a server for your selected RAM amount. Please
|
||||
try again later, or try a different amount.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<InfoIcon class="hidden sm:block" />
|
||||
<span class="text-sm text-secondary">
|
||||
Storage and vCPUs are currently not configurable.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 1 : 0)"
|
||||
class="md:w-[600px]"
|
||||
>
|
||||
<div>
|
||||
<p class="my-2 text-lg font-bold">Choose billing interval</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
@@ -74,14 +197,16 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 pt-4">
|
||||
<InfoIcon />
|
||||
<InfoIcon class="hidden sm:block" />
|
||||
<span class="text-sm text-secondary">
|
||||
Final price and currency will be based on your selected payment method.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="purchaseModalStep === 1">
|
||||
<template
|
||||
v-if="purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 2 : 1)"
|
||||
>
|
||||
<div
|
||||
v-show="loadingPaymentMethodModal !== 2"
|
||||
class="flex min-h-[16rem] items-center justify-center md:w-[600px]"
|
||||
@@ -93,24 +218,48 @@
|
||||
<div id="payment-element" class="mt-4"></div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="purchaseModalStep === 2" class="md:w-[650px]">
|
||||
<div
|
||||
v-if="purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 3 : 2)"
|
||||
class="md:w-[650px]"
|
||||
>
|
||||
<div v-if="mutatedProduct.metadata.type === 'pyro'" class="r-4 rounded-xl bg-bg p-4 mb-4">
|
||||
<p class="my-2 text-lg font-bold text-primary">Server details</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<img
|
||||
v-if="projectImage"
|
||||
:src="projectImage"
|
||||
alt="Project image"
|
||||
class="w-16 h-16 rounded"
|
||||
/>
|
||||
<div>
|
||||
<p v-if="projectName" class="font-bold">{{ projectName }}</p>
|
||||
<p>Server name: {{ serverName }}</p>
|
||||
<p v-if="!projectId">Loader: {{ serverLoader }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="r-4 rounded-xl bg-bg p-4">
|
||||
<p class="my-2 text-lg font-bold text-primary">Purchase details</p>
|
||||
<div class="mb-2 flex justify-between">
|
||||
<span class="text-secondary">Modrinth+ {{ selectedPlan }}</span>
|
||||
<span class="text-secondary">
|
||||
<span class="text-secondary"
|
||||
>{{ mutatedProduct.metadata.type === 'midas' ? 'Modrinth+' : 'Modrinth Servers' }}
|
||||
{{ selectedPlan }}</span
|
||||
>
|
||||
<span class="text-secondary text-end">
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }} /
|
||||
{{ selectedPlan }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-secondary">Tax</span>
|
||||
<span class="text-secondary">{{ formatPrice(locale, tax, price.currency_code) }}</span>
|
||||
<span class="text-secondary text-end">{{
|
||||
formatPrice(locale, tax, price.currency_code)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-between border-0 border-t border-solid border-code-bg pt-4">
|
||||
<span class="text-lg font-bold">Today's total</span>
|
||||
<span class="text-lg font-extrabold text-primary">
|
||||
<span class="text-lg font-extrabold text-primary text-end">
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -131,7 +280,7 @@
|
||||
class="max-w-[20rem]"
|
||||
@select="selectPaymentMethod"
|
||||
>
|
||||
<!-- TODO: Move this to component to remove duplicated code -->
|
||||
<!-- eslint-disable-next-line vue/no-template-shadow -->
|
||||
<template #singleLabel="props">
|
||||
<div class="flex items-center gap-2">
|
||||
<CardIcon v-if="props.option.type === 'card'" class="h-8 w-8" />
|
||||
@@ -163,6 +312,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- eslint-disable-next-line vue/no-template-shadow -->
|
||||
<template #option="props">
|
||||
<div class="flex items-center gap-2">
|
||||
<template v-if="props.option.id === 'new'">
|
||||
@@ -204,10 +354,22 @@
|
||||
</div>
|
||||
<p class="m-0 mt-9 text-sm text-secondary">
|
||||
<strong>By clicking "Subscribe", you are purchasing a recurring subscription.</strong>
|
||||
<br />
|
||||
You'll be charged
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }} /
|
||||
{{ selectedPlan }} plus applicable taxes starting today, until you cancel. Cancel anytime
|
||||
from your settings page.
|
||||
{{ selectedPlan }} plus applicable taxes starting today, until you cancel.
|
||||
<br />
|
||||
You can cancel anytime from your settings page.
|
||||
</p>
|
||||
<p v-if="mutatedProduct.metadata.type === 'pyro'" class="mb-2 mt-4 text-secondary">
|
||||
<Checkbox v-model="eulaAccepted" :disabled="paymentLoading">
|
||||
<label>
|
||||
I acknowledge that I have read and agree to the
|
||||
<a class="underline" target="_blank" href="https://aka.ms/MinecraftEULA">
|
||||
Minecraft EULA
|
||||
</a>
|
||||
</label>
|
||||
</Checkbox>
|
||||
</p>
|
||||
</div>
|
||||
<div class="input-group push-right pt-4">
|
||||
@@ -216,17 +378,48 @@
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="
|
||||
paymentLoading ||
|
||||
(mutatedProduct.metadata.type === 'pyro' && !projectId && !serverName) ||
|
||||
(customServer && !customMatchingProduct)
|
||||
"
|
||||
@click="nextStep"
|
||||
>
|
||||
<RightArrowIcon />
|
||||
{{ mutatedProduct.metadata.type === 'pyro' && !projectId ? 'Next' : 'Select' }}
|
||||
</button>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 1 : 0)
|
||||
"
|
||||
>
|
||||
<button
|
||||
class="btn"
|
||||
@click="
|
||||
purchaseModalStep =
|
||||
mutatedProduct.metadata.type === 'pyro' && !projectId ? 0 : purchaseModalStep
|
||||
"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button class="btn btn-primary" :disabled="paymentLoading" @click="beginPurchaseFlow(true)">
|
||||
<RightArrowIcon />
|
||||
Select
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="purchaseModalStep === 1">
|
||||
<template
|
||||
v-else-if="
|
||||
purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 2 : 1)
|
||||
"
|
||||
>
|
||||
<button
|
||||
class="btn"
|
||||
@click="
|
||||
() => {
|
||||
purchaseModalStep = 0
|
||||
purchaseModalStep = mutatedProduct.metadata.type === 'pyro' && !projectId ? 1 : 0
|
||||
loadingPaymentMethodModal = 0
|
||||
paymentLoading = false
|
||||
}
|
||||
@@ -239,20 +432,34 @@
|
||||
Continue
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="purchaseModalStep === 2">
|
||||
<template
|
||||
v-else-if="
|
||||
purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 3 : 2)
|
||||
"
|
||||
>
|
||||
<button class="btn" @click="$refs.purchaseModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary" :disabled="paymentLoading" @click="submitPayment">
|
||||
<button
|
||||
v-if="mutatedProduct.metadata.type === 'pyro'"
|
||||
class="btn btn-primary"
|
||||
:disabled="paymentLoading || !eulaAccepted"
|
||||
@click="submitPayment"
|
||||
>
|
||||
<CheckCircleIcon /> Subscribe
|
||||
</button>
|
||||
<!-- Default Subscribe Button, so M+ still works -->
|
||||
<button v-else class="btn btn-primary" :disabled="paymentLoading" @click="submitPayment">
|
||||
<CheckCircleIcon /> Subscribe
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { ref, computed, nextTick, reactive, watch } from 'vue'
|
||||
import NewModal from '../modal/NewModal.vue'
|
||||
import {
|
||||
CardIcon,
|
||||
@@ -260,6 +467,7 @@ import {
|
||||
ChevronRightIcon,
|
||||
CurrencyIcon,
|
||||
InfoIcon,
|
||||
IssuesIcon,
|
||||
PayPalIcon,
|
||||
PlusIcon,
|
||||
RadioButtonChecked,
|
||||
@@ -271,6 +479,8 @@ import AnimatedLogo from '../brand/AnimatedLogo.vue'
|
||||
import { getCurrency, calculateSavings, formatPrice, createStripeElements } from '@modrinth/utils'
|
||||
import { useVIntl, defineMessages } from '@vintl/vintl'
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
import Checkbox from '../base/Checkbox.vue'
|
||||
import Slider from '../base/Slider.vue'
|
||||
|
||||
const { locale, formatMessage } = useVIntl()
|
||||
|
||||
@@ -311,6 +521,30 @@ const props = defineProps({
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
projectName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
projectImage: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
projectId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
versionId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
serverName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
customServer: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -372,43 +606,19 @@ let stripe = null
|
||||
let elements = null
|
||||
|
||||
const purchaseModal = ref()
|
||||
|
||||
const primaryPaymentMethodId = computed(() => {
|
||||
if (
|
||||
props.customer &&
|
||||
props.customer.invoice_settings &&
|
||||
props.customer.invoice_settings.default_payment_method
|
||||
) {
|
||||
return props.customer.invoice_settings.default_payment_method
|
||||
} else if (props.paymentMethods && props.paymentMethods[0] && props.paymentMethods[0].id) {
|
||||
return props.paymentMethods[0].id
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
stripe = Stripe(props.publishableKey)
|
||||
|
||||
selectedPlan.value = 'yearly'
|
||||
|
||||
purchaseModalStep.value = 0
|
||||
loadingPaymentMethodModal.value = 0
|
||||
paymentLoading.value = false
|
||||
|
||||
purchaseModal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
const purchaseModalStep = ref(0)
|
||||
const loadingPaymentMethodModal = ref(0)
|
||||
const paymentLoading = ref(false)
|
||||
|
||||
const selectedPlan = ref('yearly')
|
||||
const currency = computed(() => getCurrency(props.country))
|
||||
|
||||
const price = ref(props.product.prices.find((x) => x.currency_code === currency.value))
|
||||
const price = computed(() => {
|
||||
return (
|
||||
mutatedProduct.value?.prices?.find((x) => x.currency_code === currency.value) ??
|
||||
mutatedProduct.value?.prices?.find((x) => x.currency_code === 'USD') ??
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
const clientSecret = ref()
|
||||
const paymentIntentId = ref()
|
||||
@@ -416,6 +626,39 @@ const confirmationToken = ref()
|
||||
const tax = ref()
|
||||
const total = ref()
|
||||
|
||||
const serverName = ref(props.serverName || '')
|
||||
const serverLoader = ref('Vanilla')
|
||||
const eulaAccepted = ref(false)
|
||||
|
||||
const mutatedProduct = ref({ ...props.product })
|
||||
const customMatchingProduct = ref()
|
||||
|
||||
const customServerConfig = reactive({
|
||||
ramInGb: 4,
|
||||
storageGbFormatted: computed(() => `${mutatedProduct.value.metadata.storage / 1024} GB`),
|
||||
ram: computed(() => customServerConfig.ramInGb * 1024),
|
||||
})
|
||||
|
||||
const updateCustomServerProduct = () => {
|
||||
if (props.customServer) {
|
||||
customMatchingProduct.value = props.product.find(
|
||||
(product) => product.metadata.ram === customServerConfig.ram,
|
||||
)
|
||||
|
||||
if (customMatchingProduct.value) mutatedProduct.value = { ...customMatchingProduct.value }
|
||||
}
|
||||
}
|
||||
|
||||
if (props.customServer) {
|
||||
updateCustomServerProduct()
|
||||
watch(
|
||||
() => customServerConfig.ram,
|
||||
() => {
|
||||
updateCustomServerProduct()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const selectedPaymentMethod = ref()
|
||||
const inputtedPaymentMethod = ref()
|
||||
const selectablePaymentMethods = computed(() => {
|
||||
@@ -433,7 +676,52 @@ const selectablePaymentMethods = computed(() => {
|
||||
return values
|
||||
})
|
||||
|
||||
const paymentLoading = ref(false)
|
||||
const primaryPaymentMethodId = computed(() => {
|
||||
if (
|
||||
props.customer &&
|
||||
props.customer.invoice_settings &&
|
||||
props.customer.invoice_settings.default_payment_method
|
||||
) {
|
||||
return props.customer.invoice_settings.default_payment_method
|
||||
} else if (props.paymentMethods && props.paymentMethods[0] && props.paymentMethods[0].id) {
|
||||
return props.paymentMethods[0].id
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const metadata = computed(() => {
|
||||
if (mutatedProduct.value.metadata.type === 'pyro') {
|
||||
return {
|
||||
type: 'pyro',
|
||||
server_name: serverName.value,
|
||||
source:
|
||||
props.projectId && props.versionId
|
||||
? {
|
||||
project_id: props.projectId,
|
||||
version_id: props.versionId,
|
||||
}
|
||||
: {
|
||||
loader: serverLoader.value,
|
||||
loader_version: '',
|
||||
game_version: 'latest',
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
function nextStep() {
|
||||
if (
|
||||
mutatedProduct.value.metadata.type === 'pyro' &&
|
||||
!props.projectId &&
|
||||
purchaseModalStep.value === 0
|
||||
) {
|
||||
purchaseModalStep.value = 1
|
||||
} else {
|
||||
beginPurchaseFlow(true)
|
||||
}
|
||||
}
|
||||
|
||||
async function beginPurchaseFlow(skip = false) {
|
||||
if (!props.customer) {
|
||||
@@ -446,11 +734,13 @@ async function beginPurchaseFlow(skip = false) {
|
||||
paymentLoading.value = true
|
||||
await refreshPayment(null, primaryPaymentMethodId.value)
|
||||
paymentLoading.value = false
|
||||
purchaseModalStep.value = 2
|
||||
purchaseModalStep.value =
|
||||
mutatedProduct.value.metadata.type === 'pyro' && !props.projectId ? 3 : 2
|
||||
} else {
|
||||
try {
|
||||
loadingPaymentMethodModal.value = 0
|
||||
purchaseModalStep.value = 1
|
||||
purchaseModalStep.value =
|
||||
mutatedProduct.value.metadata.type === 'pyro' && !props.projectId ? 2 : 1
|
||||
|
||||
await nextTick()
|
||||
|
||||
@@ -486,7 +776,6 @@ async function createConfirmationToken() {
|
||||
|
||||
if (error) {
|
||||
props.onError(error)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -509,7 +798,8 @@ async function validatePayment() {
|
||||
|
||||
loadingPaymentMethodModal.value = 0
|
||||
confirmationToken.value = await createConfirmationToken()
|
||||
purchaseModalStep.value = 2
|
||||
purchaseModalStep.value =
|
||||
mutatedProduct.value.metadata.type === 'pyro' && !props.projectId ? 3 : 2
|
||||
paymentLoading.value = false
|
||||
}
|
||||
|
||||
@@ -542,10 +832,11 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
||||
const result = await props.sendBillingRequest({
|
||||
charge: {
|
||||
type: 'new',
|
||||
product_id: props.product.id,
|
||||
product_id: mutatedProduct.value.id,
|
||||
interval: selectedPlan.value,
|
||||
},
|
||||
existing_payment_intent: paymentIntentId.value,
|
||||
metadata: metadata.value,
|
||||
...base,
|
||||
})
|
||||
|
||||
@@ -554,7 +845,7 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
||||
clientSecret.value = result.client_secret
|
||||
}
|
||||
|
||||
price.value = props.product.prices.find((x) => x.id === result.price_id)
|
||||
price.value = mutatedProduct.value.prices.find((x) => x.id === result.price_id)
|
||||
currency.value = price.value.currency_code
|
||||
tax.value = result.tax
|
||||
total.value = result.total
|
||||
@@ -585,5 +876,22 @@ async function submitPayment() {
|
||||
}
|
||||
paymentLoading.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
stripe = Stripe(props.publishableKey)
|
||||
|
||||
selectedPlan.value = 'yearly'
|
||||
serverName.value = props.serverName || ''
|
||||
serverLoader.value = 'Vanilla'
|
||||
|
||||
purchaseModalStep.value = 0
|
||||
loadingPaymentMethodModal.value = 0
|
||||
paymentLoading.value = false
|
||||
|
||||
purchaseModal.value.show()
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -148,8 +148,9 @@ defineExpose({
|
||||
position: fixed;
|
||||
box-shadow: var(--shadow-raised), var(--shadow-inset);
|
||||
border-radius: var(--radius-lg);
|
||||
background-color: var(--color-raised-bg);
|
||||
max-height: calc(100% - 2 * var(--gap-lg));
|
||||
overflow-y: auto;
|
||||
overflow-y: visible;
|
||||
width: 600px;
|
||||
pointer-events: auto;
|
||||
|
||||
@@ -166,10 +167,6 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: var(--color-raised-bg);
|
||||
}
|
||||
|
||||
transform: translateY(50vh);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
@@ -59,6 +59,7 @@ const props = withDefaults(
|
||||
warnOnClose?: boolean
|
||||
header?: string
|
||||
onHide?: () => void
|
||||
onShow?: () => void
|
||||
}>(),
|
||||
{
|
||||
type: true,
|
||||
@@ -67,14 +68,29 @@ const props = withDefaults(
|
||||
closeOnEsc: true,
|
||||
warnOnClose: false,
|
||||
onHide: () => {},
|
||||
onShow: () => {},
|
||||
},
|
||||
)
|
||||
|
||||
const open = ref(false)
|
||||
const visible = ref(false)
|
||||
|
||||
// make modal opening not shift page when there's a vertical scrollbar
|
||||
function addBodyPadding() {
|
||||
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth
|
||||
if (scrollBarWidth > 0) {
|
||||
document.body.style.paddingRight = `${scrollBarWidth}px`
|
||||
} else {
|
||||
document.body.style.paddingRight = ''
|
||||
}
|
||||
}
|
||||
|
||||
function show(event?: MouseEvent) {
|
||||
props.onShow()
|
||||
open.value = true
|
||||
|
||||
addBodyPadding()
|
||||
document.body.style.overflow = 'hidden'
|
||||
window.addEventListener('mousedown', updateMousePosition)
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
if (event) {
|
||||
@@ -91,6 +107,8 @@ function show(event?: MouseEvent) {
|
||||
function hide() {
|
||||
props.onHide()
|
||||
visible.value = false
|
||||
document.body.style.overflow = ''
|
||||
document.body.style.paddingRight = ''
|
||||
window.removeEventListener('mousedown', updateMousePosition)
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
setTimeout(() => {
|
||||
@@ -143,12 +161,6 @@ function handleKeyDown(event: KeyboardEvent) {
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-out;
|
||||
background: linear-gradient(to bottom, rgba(29, 48, 43, 0.52) 0%, rgba(14, 21, 26, 0.95) 100%);
|
||||
//transform: translate(
|
||||
// calc((-50vw + var(--_mouse-x, 50vw) * 1px) / 2),
|
||||
// calc((-50vh + var(--_mouse-y, 50vh) * 1px) / 2)
|
||||
// )
|
||||
// scaleX(0.8) scaleY(0.5);
|
||||
border-radius: 180px;
|
||||
filter: blur(5px);
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
@@ -159,8 +171,6 @@ function handleKeyDown(event: KeyboardEvent) {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
backdrop-filter: blur(5px);
|
||||
transform: translate(0, 0) scaleX(1) scaleY(1);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
&.noblur {
|
||||
|
||||
Reference in New Issue
Block a user