Merge commit '5ab12634954dabea006912efcdeb4d30992858fd' into feature-clean
@@ -1,6 +1,6 @@
|
||||
//! Configuration structs
|
||||
|
||||
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/";
|
||||
pub const MODRINTH_API_URL_V3: &str = "https://api.modrinth.com/v3/";
|
||||
pub const MODRINTH_API_URL: &str = "https://staging-api.modrinth.com/v2/";
|
||||
pub const MODRINTH_API_URL_V3: &str = "https://staging-api.modrinth.com/v3/";
|
||||
|
||||
pub const META_URL: &str = "https://launcher-meta.modrinth.com/";
|
||||
|
||||
1
packages/assets/external/pyro.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.1355 28.2805C12.1355 27.5892 12.1355 27.2435 12.1946 26.9561C12.437 25.7757 13.3896 24.853 14.6082 24.6182C14.905 24.561 15.2618 24.561 15.9755 24.561C16.6892 24.561 17.0461 24.561 17.3428 24.6182C18.5615 24.853 19.5141 25.7757 19.7565 26.9561C19.8155 27.2435 19.8155 27.5892 19.8155 28.2805V29.5203C19.8155 30.4473 19.8155 30.9109 19.6097 31.2561C19.4749 31.4823 19.281 31.6701 19.0475 31.8007C18.6911 32 18.2126 32 17.2555 32H14.6955C13.7385 32 13.26 32 12.9035 31.8007C12.67 31.6701 12.4761 31.4823 12.3413 31.2561C12.1355 30.9109 12.1355 30.4473 12.1355 29.5203V28.2805Z" fill="currentColor"></path><path d="M6.3827 32H9.01425C9.31219 32 9.46117 32 9.53801 31.9082C9.61485 31.8165 9.58266 31.6686 9.51829 31.3727C6.98423 19.7258 18.7021 22.3442 20.3986 14.286C21.2645 10.1733 18.7998 4.65319 18.9453 0.817638C18.9635 0.33689 18.9727 0.0965167 18.8384 0.0220137C18.7042 -0.0524892 18.5103 0.0670423 18.1226 0.306105C12.6454 3.68306 8.65384 10.1385 10.1126 14.0992C10.3356 14.7047 10.4472 15.0074 10.3061 15.118C10.1651 15.2285 9.92652 15.086 9.44929 14.801C8.67327 14.3375 7.75105 13.5788 7.10995 12.3833C6.92186 12.0326 6.82781 11.8572 6.68983 11.844C6.55184 11.8307 6.43328 11.9737 6.19616 12.2595C-0.695594 20.5682 2.0972 28.8655 6.07761 31.9001C6.14113 31.9485 6.17289 31.9727 6.2136 31.9863C6.2543 32 6.2971 32 6.3827 32Z" fill="currentColor"></path><path d="M23.5502 13.1095C23.903 16.9267 21.5318 19.5377 18.8234 21.3464C18.3506 21.6621 18.1142 21.82 18.1346 21.9704C18.155 22.1208 18.4376 22.2182 19.0027 22.413C22.614 23.6579 22.7781 27.2264 22.4791 31.4665C22.4614 31.7172 22.4526 31.8426 22.5285 31.9213C22.6044 32 22.7334 32 22.9913 32H25.2472C25.3155 32 25.3497 32 25.3828 31.9912C25.4159 31.9825 25.4452 31.9657 25.5039 31.9322C33.5906 27.3121 29.2489 16.2756 24.3156 12.6327C23.9559 12.3671 23.7761 12.2343 23.6299 12.3124C23.4838 12.3906 23.5059 12.6302 23.5502 13.1095Z" fill="currentColor"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
3
packages/assets/icons/cloud.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M1 12.5A4.5 4.5 0 0 0 5.5 17H15a4 4 0 0 0 1.866-7.539 3.504 3.504 0 0 0-4.504-4.272A4.5 4.5 0 0 0 4.06 8.235 4.502 4.502 0 0 0 1 12.5Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 238 B |
3
packages/assets/icons/cog.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M7.84 1.804A1 1 0 0 1 8.82 1h2.36a1 1 0 0 1 .98.804l.331 1.652a6.993 6.993 0 0 1 1.929 1.115l1.598-.54a1 1 0 0 1 1.186.447l1.18 2.044a1 1 0 0 1-.205 1.251l-1.267 1.113a7.047 7.047 0 0 1 0 2.228l1.267 1.113a1 1 0 0 1 .206 1.25l-1.18 2.045a1 1 0 0 1-1.187.447l-1.598-.54a6.993 6.993 0 0 1-1.929 1.115l-.33 1.652a1 1 0 0 1-.98.804H8.82a1 1 0 0 1-.98-.804l-.331-1.652a6.993 6.993 0 0 1-1.929-1.115l-1.598.54a1 1 0 0 1-1.186-.447l-1.18-2.044a1 1 0 0 1 .205-1.251l1.267-1.114a7.05 7.05 0 0 1 0-2.227L1.821 7.773a1 1 0 0 1-.206-1.25l1.18-2.045a1 1 0 0 1 1.187-.447l1.598.54A6.992 6.992 0 0 1 7.51 3.456l.33-1.652ZM10 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 785 B |
14
packages/assets/icons/cpu.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="absolute right-8 top-8 size-8"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 527 B |
3
packages/assets/icons/cube.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10.362 1.093a.75.75 0 0 0-.724 0L2.523 5.018 10 9.143l7.477-4.125-7.115-3.925ZM18 6.443l-7.25 4v8.25l6.862-3.786A.75.75 0 0 0 18 14.25V6.443ZM9.25 18.693v-8.25l-7.25-4v7.807a.75.75 0 0 0 .388.657l6.862 3.786Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 313 B |
14
packages/assets/icons/db.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="absolute right-8 top-8 size-8"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 633 B |
18
packages/assets/icons/loader.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
fill-rule="evenodd"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
clip-rule="evenodd"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="none" d="M0 0h24v24H0z"></path>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="23"
|
||||
d="m820 761-85.6-87.6c-4.6-4.7-10.4-9.6-25.9 1-19.9 13.6-8.4 21.9-5.2 25.4 8.2 9 84.1 89 97.2 104 2.5 2.8-20.3-22.5-6.5-39.7 5.4-7 18-12 26-3 6.5 7.3 10.7 18-3.4 29.7-24.7 20.4-102 82.4-127 103-12.5 10.3-28.5 2.3-35.8-6-7.5-8.9-30.6-34.6-51.3-58.2-5.5-6.3-4.1-19.6 2.3-25 35-30.3 91.9-73.8 111.9-90.8"
|
||||
transform="matrix(.08671 0 0 .0867 -49.8 -56)"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 716 B |
1
packages/assets/icons/package-closed.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-package"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"/><path d="M12 22V12"/><path d="m3.3 7 7.703 4.734a2 2 0 0 0 1.994 0L20.7 7"/><path d="m7.5 4.27 9 5.15"/></svg>
|
||||
|
After Width: | Height: | Size: 454 B |
1
packages/assets/icons/plug.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plug"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z"/></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
14
packages/assets/icons/test.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 522 B |
@@ -23,7 +23,8 @@ import _PayPalIcon from './external/paypal.svg?component'
|
||||
import _RedditIcon from './external/reddit.svg?component'
|
||||
import _TwitterIcon from './external/twitter.svg?component'
|
||||
import _WindowsIcon from './external/windows.svg?component'
|
||||
import _YouTubeIcon from './icons/youtube.svg?component'
|
||||
import _YouTubeIcon from './external/youtube.svg?component'
|
||||
import _PyroIcon from './external/pyro.svg?component'
|
||||
|
||||
// Icons
|
||||
import _AlignLeftIcon from './icons/align-left.svg?component'
|
||||
@@ -111,8 +112,10 @@ import _NewspaperIcon from './icons/newspaper.svg?component'
|
||||
import _OmorphiaIcon from './icons/omorphia.svg?component'
|
||||
import _OrganizationIcon from './icons/organization.svg?component'
|
||||
import _PackageOpenIcon from './icons/package-open.svg?component'
|
||||
import _PackageClosedIcon from './icons/package-closed.svg?component'
|
||||
import _PaintBrushIcon from './icons/paintbrush.svg?component'
|
||||
import _PlayIcon from './icons/play.svg?component'
|
||||
import _PlugIcon from './icons/plug.svg?component'
|
||||
import _PlusIcon from './icons/plus.svg?component'
|
||||
import _RadioButtonIcon from './icons/radio-button.svg?component'
|
||||
import _RadioButtonChecked from './icons/radio-button-checked.svg?component'
|
||||
@@ -160,6 +163,13 @@ import _XIcon from './icons/x.svg?component'
|
||||
import _XCircleIcon from './icons/x-circle.svg?component'
|
||||
import _ZoomInIcon from './icons/zoom-in.svg?component'
|
||||
import _ZoomOutIcon from './icons/zoom-out.svg?component'
|
||||
import _CubeIcon from './icons/cube.svg?component'
|
||||
import _CloudIcon from './icons/cloud.svg?component'
|
||||
import _CogIcon from './icons/cog.svg?component'
|
||||
import _CPUIcon from './icons/cpu.svg?component'
|
||||
import _DBIcon from './icons/db.svg?component'
|
||||
import _LoaderIcon from './icons/loader.svg?component'
|
||||
import _ImportIcon from './icons/import.svg?component'
|
||||
|
||||
// Editor Icons
|
||||
import _BoldIcon from './icons/bold.svg?component'
|
||||
@@ -208,6 +218,7 @@ export const RedditIcon = _RedditIcon
|
||||
export const TwitterIcon = _TwitterIcon
|
||||
export const WindowsIcon = _WindowsIcon
|
||||
export const YouTubeIcon = _YouTubeIcon
|
||||
export const PyroIcon = _PyroIcon
|
||||
export const AlignLeftIcon = _AlignLeftIcon
|
||||
export const ArchiveIcon = _ArchiveIcon
|
||||
export const ArrowBigUpDashIcon = _ArrowBigUpDashIcon
|
||||
@@ -293,8 +304,10 @@ export const NewspaperIcon = _NewspaperIcon
|
||||
export const OmorphiaIcon = _OmorphiaIcon
|
||||
export const OrganizationIcon = _OrganizationIcon
|
||||
export const PackageOpenIcon = _PackageOpenIcon
|
||||
export const PackageClosedIcon = _PackageClosedIcon
|
||||
export const PaintBrushIcon = _PaintBrushIcon
|
||||
export const PlayIcon = _PlayIcon
|
||||
export const PlugIcon = _PlugIcon
|
||||
export const PlusIcon = _PlusIcon
|
||||
export const RadioButtonIcon = _RadioButtonIcon
|
||||
export const RadioButtonChecked = _RadioButtonChecked
|
||||
@@ -351,5 +364,12 @@ export const TextQuoteIcon = _TextQuoteIcon
|
||||
export const Heading1Icon = _Heading1Icon
|
||||
export const Heading2Icon = _Heading2Icon
|
||||
export const Heading3Icon = _Heading3Icon
|
||||
export const CubeIcon = _CubeIcon
|
||||
export const CloudIcon = _CloudIcon
|
||||
export const CogIcon = _CogIcon
|
||||
export const CPUIcon = _CPUIcon
|
||||
export const DBIcon = _DBIcon
|
||||
export const LoaderIcon = _LoaderIcon
|
||||
export const ImportIcon = _ImportIcon
|
||||
export const CardIcon = _CardIcon
|
||||
export const SparklesIcon = _SparklesIcon
|
||||
|
||||
@@ -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,126 @@
|
||||
<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="customMinRam"
|
||||
:max="customMaxRam"
|
||||
:step="2"
|
||||
unit="GB"
|
||||
/>
|
||||
<div class="font-semibold text-nowrap"></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="customMatchingProduct && !customOutOfStock"
|
||||
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 v-if="customOutOfStock && customMatchingProduct" class="flex flex-col gap-2">
|
||||
<div class="font-semibold">This plan is currently out of stock</div>
|
||||
<div class="font-normal">
|
||||
We are currently
|
||||
<a :href="outOfStockUrl" class="underline" target="_blank">out of capacity</a>
|
||||
for your selected RAM amount. Please try again later, or try a different amount.
|
||||
</div>
|
||||
</div>
|
||||
<div v-else 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 +208,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 +229,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 +291,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 +323,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 +365,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 +389,48 @@
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="
|
||||
paymentLoading ||
|
||||
(mutatedProduct.metadata.type === 'pyro' && !projectId && !serverName) ||
|
||||
customAllowedToContinue
|
||||
"
|
||||
@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 +443,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 +478,7 @@ import {
|
||||
ChevronRightIcon,
|
||||
CurrencyIcon,
|
||||
InfoIcon,
|
||||
IssuesIcon,
|
||||
PayPalIcon,
|
||||
PlusIcon,
|
||||
RadioButtonChecked,
|
||||
@@ -271,6 +490,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 +532,40 @@ 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,
|
||||
},
|
||||
fetchCapacityStatuses: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
outOfStockUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -372,43 +627,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 +647,73 @@ 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 customMinRam = ref(0)
|
||||
const customMaxRam = ref(0)
|
||||
const customMatchingProduct = ref()
|
||||
const customOutOfStock = ref(false)
|
||||
const customLoading = ref(true)
|
||||
const customAllowedToContinue = computed(
|
||||
() =>
|
||||
props.customServer &&
|
||||
(!customMatchingProduct.value || customLoading.value || customOutOfStock.value),
|
||||
)
|
||||
|
||||
const customServerConfig = reactive({
|
||||
ramInGb: 4,
|
||||
storageGbFormatted: computed(() => `${mutatedProduct.value.metadata.storage / 1024} GB`),
|
||||
ram: computed(() => customServerConfig.ramInGb * 1024),
|
||||
})
|
||||
|
||||
const updateCustomServerProduct = () => {
|
||||
customMatchingProduct.value = props.product.find(
|
||||
(product) => product.metadata.ram === customServerConfig.ram,
|
||||
)
|
||||
|
||||
if (customMatchingProduct.value) mutatedProduct.value = { ...customMatchingProduct.value }
|
||||
}
|
||||
|
||||
let updateCustomServerStockTimeout = null
|
||||
const updateCustomServerStock = async () => {
|
||||
if (updateCustomServerStockTimeout) {
|
||||
clearTimeout(updateCustomServerStockTimeout)
|
||||
customLoading.value = true
|
||||
}
|
||||
|
||||
updateCustomServerStockTimeout = setTimeout(async () => {
|
||||
if (props.fetchCapacityStatuses) {
|
||||
const capacityStatus = await props.fetchCapacityStatuses(mutatedProduct.value)
|
||||
if (capacityStatus.custom?.available === 0) {
|
||||
customOutOfStock.value = true
|
||||
} else {
|
||||
customOutOfStock.value = false
|
||||
}
|
||||
customLoading.value = false
|
||||
} else {
|
||||
console.error('No fetchCapacityStatuses function provided.')
|
||||
customOutOfStock.value = true
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
if (props.customServer) {
|
||||
const ramValues = props.product.map((product) => product.metadata.ram / 1024)
|
||||
customMinRam.value = Math.min(...ramValues)
|
||||
customMaxRam.value = Math.max(...ramValues)
|
||||
|
||||
const updateProductAndStock = () => {
|
||||
updateCustomServerProduct()
|
||||
updateCustomServerStock()
|
||||
}
|
||||
|
||||
updateProductAndStock()
|
||||
watch(() => customServerConfig.ram, updateProductAndStock)
|
||||
}
|
||||
|
||||
const selectedPaymentMethod = ref()
|
||||
const inputtedPaymentMethod = ref()
|
||||
const selectablePaymentMethods = computed(() => {
|
||||
@@ -433,7 +731,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 null
|
||||
})
|
||||
|
||||
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 +789,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 +831,6 @@ async function createConfirmationToken() {
|
||||
|
||||
if (error) {
|
||||
props.onError(error)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -509,7 +853,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 +887,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 +900,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 +931,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 {
|
||||
|
||||
4
packages/utils/index.d.ts
vendored
@@ -1,6 +1,10 @@
|
||||
export const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
type Base62Char = (typeof BASE62_CHARS)[number]
|
||||
|
||||
export function formatPrice(locale: string, price: number, currency: string): string
|
||||
|
||||
export function getCurrency(userCountry: string): string
|
||||
|
||||
declare global {
|
||||
type ModrinthId = `${Base62Char}`[]
|
||||
|
||||
|
||||