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:
Elizabeth
2024-11-02 23:14:00 -05:00
committed by GitHub
parent f165665a35
commit 185dd47668
126 changed files with 19390 additions and 432 deletions

View File

@@ -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
View 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

View File

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 429 B

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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'
@@ -196,6 +206,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
@@ -281,8 +292,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
@@ -339,5 +352,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

View File

@@ -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,

View File

@@ -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>

View File

@@ -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,

View File

@@ -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>

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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}`[]