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>
@@ -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'
|
||||
@@ -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
|
||||
|
||||
@@ -6,9 +6,11 @@ const props = withDefaults(
|
||||
color?: 'standard' | 'brand' | 'red' | 'orange' | 'green' | 'blue' | 'purple'
|
||||
size?: 'standard' | 'large'
|
||||
circular?: boolean
|
||||
type?: 'standard' | 'outlined' | 'transparent'
|
||||
type?: 'standard' | 'outlined' | 'transparent' | 'highlight'
|
||||
colorFill?: 'auto' | 'background' | 'text' | 'none'
|
||||
hoverColorFill?: 'auto' | 'background' | 'text' | 'none'
|
||||
highlightedStyle?: 'main-nav-primary' | 'main-nav-secondary'
|
||||
highlighted?: boolean
|
||||
}>(),
|
||||
{
|
||||
color: 'standard',
|
||||
@@ -17,23 +19,25 @@ const props = withDefaults(
|
||||
type: 'standard',
|
||||
colorFill: 'auto',
|
||||
hoverColorFill: 'auto',
|
||||
highlightedStyle: 'main-nav-primary',
|
||||
highlighted: false,
|
||||
},
|
||||
)
|
||||
|
||||
const colorVar = computed(() => {
|
||||
switch (props.color) {
|
||||
case 'brand':
|
||||
return 'var(--color-brand)'
|
||||
return props.type === 'highlight' ? 'var(--color-brand-highlight)' : 'var(--color-brand)'
|
||||
case 'red':
|
||||
return 'var(--color-red)'
|
||||
return props.type === 'highlight' ? 'var(--color-red-highlight)' : 'var(--color-red)'
|
||||
case 'orange':
|
||||
return 'var(--color-orange)'
|
||||
return props.type === 'highlight' ? 'var(--color-orange-highlight)' : 'var(--color-orange)'
|
||||
case 'green':
|
||||
return 'var(--color-green)'
|
||||
return props.type === 'highlight' ? 'var(--color-green-highlight)' : 'var(--color-green)'
|
||||
case 'blue':
|
||||
return 'var(--color-blue)'
|
||||
return props.type === 'highlight' ? 'var(--color-blue-highlight)' : 'var(--color-blue)'
|
||||
case 'purple':
|
||||
return 'var(--color-purple)'
|
||||
return props.type === 'highlight' ? 'var(--color-purple-highlight)' : 'var(--color-purple)'
|
||||
case 'standard':
|
||||
default:
|
||||
return null
|
||||
@@ -108,7 +112,11 @@ function setColorFill(
|
||||
if (colorVar.value) {
|
||||
if (fill === 'background') {
|
||||
colors.bg = colorVar.value
|
||||
colors.text = 'var(--color-accent-contrast)'
|
||||
if (props.type === 'highlight') {
|
||||
colors.text = 'var(--color-contrast)'
|
||||
} else {
|
||||
colors.text = 'var(--color-accent-contrast)'
|
||||
}
|
||||
} else if (fill === 'text') {
|
||||
colors.text = colorVar.value
|
||||
}
|
||||
@@ -117,6 +125,22 @@ function setColorFill(
|
||||
}
|
||||
|
||||
const colorVariables = computed(() => {
|
||||
if (props.highlighted) {
|
||||
let colors = {
|
||||
bg:
|
||||
props.highlightedStyle === 'main-nav-primary'
|
||||
? 'var(--color-brand-highlight)'
|
||||
: 'var(--color-button-bg)',
|
||||
text: 'var(--color-contrast)',
|
||||
icon:
|
||||
props.highlightedStyle === 'main-nav-primary'
|
||||
? 'var(--color-brand)'
|
||||
: 'var(--color-contrast)',
|
||||
}
|
||||
let hoverColors = JSON.parse(JSON.stringify(colors))
|
||||
return `--_bg: ${colors.bg}; --_text: ${colors.text}; --_icon: ${colors.icon}; --_hover-bg: ${hoverColors.bg}; --_hover-text: ${hoverColors.text}; --_hover-icon: ${hoverColors.icon};`
|
||||
}
|
||||
|
||||
let colors = {
|
||||
bg: 'var(--color-button-bg)',
|
||||
text: 'var(--color-base)',
|
||||
@@ -176,6 +200,10 @@ const colorVariables = computed(() => {
|
||||
background-color 0.25s ease-in-out,
|
||||
color 0.25s ease-in-out;
|
||||
|
||||
svg:first-child {
|
||||
color: var(--_icon, var(--_text));
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&[disabled='true'],
|
||||
&.disabled,
|
||||
|
||||
@@ -104,19 +104,30 @@ defineExpose({
|
||||
})
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (!dropdown.value) return
|
||||
|
||||
const isContextMenuClick = event.button === 2 || event.which === 3
|
||||
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
||||
if (
|
||||
dropdown.value.$el !== event.target &&
|
||||
!elements.includes(dropdown.value.$el) &&
|
||||
!dropdown.value.contains(event.target)
|
||||
(dropdown.value !== event.target &&
|
||||
!elements.includes(dropdown.value) &&
|
||||
!dropdown.value.contains(event.target)) ||
|
||||
isContextMenuClick
|
||||
) {
|
||||
dropdownVisible.value = false
|
||||
emit('close')
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener('mouseup', handleClickOutside)
|
||||
window.addEventListener('resize', updateDirection)
|
||||
window.addEventListener('scroll', updateDirection)
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
@@ -125,16 +136,11 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener('mouseup', handleClickOutside)
|
||||
window.removeEventListener('resize', updateDirection)
|
||||
window.removeEventListener('scroll', updateDirection)
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === 'Escape') {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -140,6 +140,9 @@ const onInput = (value: string) => {
|
||||
border-radius: var(--radius-sm);
|
||||
height: 0.25rem;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
min-height: 0px;
|
||||
box-shadow: none;
|
||||
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
|
||||
@@ -3,23 +3,45 @@
|
||||
<template #title>
|
||||
<span class="text-contrast text-xl font-extrabold">
|
||||
<template v-if="product.metadata.type === 'midas'">Subscribe to Modrinth Plus!</template>
|
||||
<template v-else-if="product.metadata.type === 'pyro'"
|
||||
>Subscribe to Modrinth Servers!</template
|
||||
>
|
||||
<template v-else>Purchase product</template>
|
||||
</span>
|
||||
</template>
|
||||
<div class="flex items-center gap-1 pb-4">
|
||||
<template v-if="product.metadata.type === 'pyro' && !projectId">
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 0,
|
||||
'font-bold': purchaseModalStep === 0,
|
||||
}"
|
||||
>
|
||||
Configure
|
||||
<span class="hidden sm:inline">server</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
</template>
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 0,
|
||||
'font-bold': purchaseModalStep === 0,
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 1 : 0),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 1 : 0),
|
||||
}"
|
||||
>
|
||||
Select plan
|
||||
{{ product.metadata.type === 'pyro' ? 'Billing' : 'Plan' }}
|
||||
<span class="hidden sm:inline">{{
|
||||
product.metadata.type === 'pyro' ? 'interval' : 'selection'
|
||||
}}</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 1,
|
||||
'font-bold': purchaseModalStep === 1,
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 2 : 1),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 2 : 1),
|
||||
}"
|
||||
>
|
||||
Payment
|
||||
@@ -27,14 +49,115 @@
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 2,
|
||||
'font-bold': purchaseModalStep === 2,
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 3 : 2),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 3 : 2),
|
||||
}"
|
||||
>
|
||||
Review
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="purchaseModalStep === 0" class="md:w-[600px]">
|
||||
<div
|
||||
v-if="product.metadata.type === 'pyro' && !projectId && purchaseModalStep === 0"
|
||||
class="md:w-[600px] flex flex-col gap-4"
|
||||
>
|
||||
<div>
|
||||
<p class="my-2 text-lg font-bold">Configure your server</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
<input v-model="serverName" placeholder="Server name" class="input" maxlength="48" />
|
||||
<!-- <DropdownSelect
|
||||
v-model="serverLoader"
|
||||
v-tooltip="'Select the mod loader for your server'"
|
||||
name="server-loader"
|
||||
:options="['Vanilla', 'Fabric', 'Forge']"
|
||||
placeholder="Select mod loader..."
|
||||
/> -->
|
||||
<div class="grid lg:grid-cols-5 grid-cols-3 gap-4">
|
||||
<button
|
||||
v-for="loader in ['Vanilla', 'Fabric', 'Forge', 'Quilt', 'NeoForge']"
|
||||
:key="loader"
|
||||
class="!h-24 btn flex !flex-col !items-center !justify-between !pt-4 !pb-3 !w-full"
|
||||
:style="{
|
||||
filter: serverLoader === loader ? 'brightness(1.5)' : '',
|
||||
}"
|
||||
@click="serverLoader = loader"
|
||||
>
|
||||
<UiServersIconsLoaderIcon :loader="loader" class="!h-12 !w-12" />
|
||||
<p class="text-lg font-bold m-0">{{ loader }}</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<InfoIcon class="hidden sm:block" />
|
||||
<span class="text-sm text-secondary">
|
||||
You can change these settings later in your server options.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customServer">
|
||||
<div class="flex gap-2 items-center">
|
||||
<p class="my-2 text-lg font-bold">Configure your RAM</p>
|
||||
<IssuesIcon
|
||||
v-if="customServerConfig.ramInGb < 4"
|
||||
v-tooltip="'This might not be enough resources for your Minecraft server.'"
|
||||
class="h-6 w-6 text-orange"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex w-full gap-2 items-center">
|
||||
<Slider
|
||||
v-model="customServerConfig.ramInGb"
|
||||
class="fix-slider"
|
||||
:min="2"
|
||||
:max="12"
|
||||
:step="2"
|
||||
unit="GB"
|
||||
/>
|
||||
<div class="font-semibold text-nowrap"></div>
|
||||
</div>
|
||||
<div v-if="customMatchingProduct" class="flex sm:flex-row flex-col gap-4 w-full">
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<div class="font-semibold">vCPUs</div>
|
||||
<input v-model="mutatedProduct.metadata.cpu" disabled class="input" />
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<div class="font-semibold">Storage</div>
|
||||
<input v-model="customServerConfig.storageGbFormatted" disabled class="input" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex justify-between rounded-2xl border-2 border-solid border-blue bg-bg-blue p-4 font-semibold text-contrast"
|
||||
>
|
||||
<div class="flex w-full justify-between gap-2">
|
||||
<div class="flex flex-row gap-4">
|
||||
<InfoIcon class="hidden flex-none h-8 w-8 text-blue sm:block" />
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="font-semibold">We can't seem to find your selected plan</div>
|
||||
<div class="font-normal">
|
||||
We are currently unable to find a server for your selected RAM amount. Please
|
||||
try again later, or try a different amount.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<InfoIcon class="hidden sm:block" />
|
||||
<span class="text-sm text-secondary">
|
||||
Storage and vCPUs are currently not configurable.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 1 : 0)"
|
||||
class="md:w-[600px]"
|
||||
>
|
||||
<div>
|
||||
<p class="my-2 text-lg font-bold">Choose billing interval</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
@@ -74,14 +197,16 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 pt-4">
|
||||
<InfoIcon />
|
||||
<InfoIcon class="hidden sm:block" />
|
||||
<span class="text-sm text-secondary">
|
||||
Final price and currency will be based on your selected payment method.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="purchaseModalStep === 1">
|
||||
<template
|
||||
v-if="purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 2 : 1)"
|
||||
>
|
||||
<div
|
||||
v-show="loadingPaymentMethodModal !== 2"
|
||||
class="flex min-h-[16rem] items-center justify-center md:w-[600px]"
|
||||
@@ -93,24 +218,48 @@
|
||||
<div id="payment-element" class="mt-4"></div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="purchaseModalStep === 2" class="md:w-[650px]">
|
||||
<div
|
||||
v-if="purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 3 : 2)"
|
||||
class="md:w-[650px]"
|
||||
>
|
||||
<div v-if="mutatedProduct.metadata.type === 'pyro'" class="r-4 rounded-xl bg-bg p-4 mb-4">
|
||||
<p class="my-2 text-lg font-bold text-primary">Server details</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<img
|
||||
v-if="projectImage"
|
||||
:src="projectImage"
|
||||
alt="Project image"
|
||||
class="w-16 h-16 rounded"
|
||||
/>
|
||||
<div>
|
||||
<p v-if="projectName" class="font-bold">{{ projectName }}</p>
|
||||
<p>Server name: {{ serverName }}</p>
|
||||
<p v-if="!projectId">Loader: {{ serverLoader }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="r-4 rounded-xl bg-bg p-4">
|
||||
<p class="my-2 text-lg font-bold text-primary">Purchase details</p>
|
||||
<div class="mb-2 flex justify-between">
|
||||
<span class="text-secondary">Modrinth+ {{ selectedPlan }}</span>
|
||||
<span class="text-secondary">
|
||||
<span class="text-secondary"
|
||||
>{{ mutatedProduct.metadata.type === 'midas' ? 'Modrinth+' : 'Modrinth Servers' }}
|
||||
{{ selectedPlan }}</span
|
||||
>
|
||||
<span class="text-secondary text-end">
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }} /
|
||||
{{ selectedPlan }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-secondary">Tax</span>
|
||||
<span class="text-secondary">{{ formatPrice(locale, tax, price.currency_code) }}</span>
|
||||
<span class="text-secondary text-end">{{
|
||||
formatPrice(locale, tax, price.currency_code)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-between border-0 border-t border-solid border-code-bg pt-4">
|
||||
<span class="text-lg font-bold">Today's total</span>
|
||||
<span class="text-lg font-extrabold text-primary">
|
||||
<span class="text-lg font-extrabold text-primary text-end">
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -131,7 +280,7 @@
|
||||
class="max-w-[20rem]"
|
||||
@select="selectPaymentMethod"
|
||||
>
|
||||
<!-- TODO: Move this to component to remove duplicated code -->
|
||||
<!-- eslint-disable-next-line vue/no-template-shadow -->
|
||||
<template #singleLabel="props">
|
||||
<div class="flex items-center gap-2">
|
||||
<CardIcon v-if="props.option.type === 'card'" class="h-8 w-8" />
|
||||
@@ -163,6 +312,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- eslint-disable-next-line vue/no-template-shadow -->
|
||||
<template #option="props">
|
||||
<div class="flex items-center gap-2">
|
||||
<template v-if="props.option.id === 'new'">
|
||||
@@ -204,10 +354,22 @@
|
||||
</div>
|
||||
<p class="m-0 mt-9 text-sm text-secondary">
|
||||
<strong>By clicking "Subscribe", you are purchasing a recurring subscription.</strong>
|
||||
<br />
|
||||
You'll be charged
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }} /
|
||||
{{ selectedPlan }} plus applicable taxes starting today, until you cancel. Cancel anytime
|
||||
from your settings page.
|
||||
{{ selectedPlan }} plus applicable taxes starting today, until you cancel.
|
||||
<br />
|
||||
You can cancel anytime from your settings page.
|
||||
</p>
|
||||
<p v-if="mutatedProduct.metadata.type === 'pyro'" class="mb-2 mt-4 text-secondary">
|
||||
<Checkbox v-model="eulaAccepted" :disabled="paymentLoading">
|
||||
<label>
|
||||
I acknowledge that I have read and agree to the
|
||||
<a class="underline" target="_blank" href="https://aka.ms/MinecraftEULA">
|
||||
Minecraft EULA
|
||||
</a>
|
||||
</label>
|
||||
</Checkbox>
|
||||
</p>
|
||||
</div>
|
||||
<div class="input-group push-right pt-4">
|
||||
@@ -216,17 +378,48 @@
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="
|
||||
paymentLoading ||
|
||||
(mutatedProduct.metadata.type === 'pyro' && !projectId && !serverName) ||
|
||||
(customServer && !customMatchingProduct)
|
||||
"
|
||||
@click="nextStep"
|
||||
>
|
||||
<RightArrowIcon />
|
||||
{{ mutatedProduct.metadata.type === 'pyro' && !projectId ? 'Next' : 'Select' }}
|
||||
</button>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 1 : 0)
|
||||
"
|
||||
>
|
||||
<button
|
||||
class="btn"
|
||||
@click="
|
||||
purchaseModalStep =
|
||||
mutatedProduct.metadata.type === 'pyro' && !projectId ? 0 : purchaseModalStep
|
||||
"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button class="btn btn-primary" :disabled="paymentLoading" @click="beginPurchaseFlow(true)">
|
||||
<RightArrowIcon />
|
||||
Select
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="purchaseModalStep === 1">
|
||||
<template
|
||||
v-else-if="
|
||||
purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 2 : 1)
|
||||
"
|
||||
>
|
||||
<button
|
||||
class="btn"
|
||||
@click="
|
||||
() => {
|
||||
purchaseModalStep = 0
|
||||
purchaseModalStep = mutatedProduct.metadata.type === 'pyro' && !projectId ? 1 : 0
|
||||
loadingPaymentMethodModal = 0
|
||||
paymentLoading = false
|
||||
}
|
||||
@@ -239,20 +432,34 @@
|
||||
Continue
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="purchaseModalStep === 2">
|
||||
<template
|
||||
v-else-if="
|
||||
purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 3 : 2)
|
||||
"
|
||||
>
|
||||
<button class="btn" @click="$refs.purchaseModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary" :disabled="paymentLoading" @click="submitPayment">
|
||||
<button
|
||||
v-if="mutatedProduct.metadata.type === 'pyro'"
|
||||
class="btn btn-primary"
|
||||
:disabled="paymentLoading || !eulaAccepted"
|
||||
@click="submitPayment"
|
||||
>
|
||||
<CheckCircleIcon /> Subscribe
|
||||
</button>
|
||||
<!-- Default Subscribe Button, so M+ still works -->
|
||||
<button v-else class="btn btn-primary" :disabled="paymentLoading" @click="submitPayment">
|
||||
<CheckCircleIcon /> Subscribe
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { ref, computed, nextTick, reactive, watch } from 'vue'
|
||||
import NewModal from '../modal/NewModal.vue'
|
||||
import {
|
||||
CardIcon,
|
||||
@@ -260,6 +467,7 @@ import {
|
||||
ChevronRightIcon,
|
||||
CurrencyIcon,
|
||||
InfoIcon,
|
||||
IssuesIcon,
|
||||
PayPalIcon,
|
||||
PlusIcon,
|
||||
RadioButtonChecked,
|
||||
@@ -271,6 +479,8 @@ import AnimatedLogo from '../brand/AnimatedLogo.vue'
|
||||
import { getCurrency, calculateSavings, formatPrice, createStripeElements } from '@modrinth/utils'
|
||||
import { useVIntl, defineMessages } from '@vintl/vintl'
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
import Checkbox from '../base/Checkbox.vue'
|
||||
import Slider from '../base/Slider.vue'
|
||||
|
||||
const { locale, formatMessage } = useVIntl()
|
||||
|
||||
@@ -311,6 +521,30 @@ const props = defineProps({
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
projectName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
projectImage: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
projectId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
versionId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
serverName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
customServer: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -372,43 +606,19 @@ let stripe = null
|
||||
let elements = null
|
||||
|
||||
const purchaseModal = ref()
|
||||
|
||||
const primaryPaymentMethodId = computed(() => {
|
||||
if (
|
||||
props.customer &&
|
||||
props.customer.invoice_settings &&
|
||||
props.customer.invoice_settings.default_payment_method
|
||||
) {
|
||||
return props.customer.invoice_settings.default_payment_method
|
||||
} else if (props.paymentMethods && props.paymentMethods[0] && props.paymentMethods[0].id) {
|
||||
return props.paymentMethods[0].id
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
stripe = Stripe(props.publishableKey)
|
||||
|
||||
selectedPlan.value = 'yearly'
|
||||
|
||||
purchaseModalStep.value = 0
|
||||
loadingPaymentMethodModal.value = 0
|
||||
paymentLoading.value = false
|
||||
|
||||
purchaseModal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
const purchaseModalStep = ref(0)
|
||||
const loadingPaymentMethodModal = ref(0)
|
||||
const paymentLoading = ref(false)
|
||||
|
||||
const selectedPlan = ref('yearly')
|
||||
const currency = computed(() => getCurrency(props.country))
|
||||
|
||||
const price = ref(props.product.prices.find((x) => x.currency_code === currency.value))
|
||||
const price = computed(() => {
|
||||
return (
|
||||
mutatedProduct.value?.prices?.find((x) => x.currency_code === currency.value) ??
|
||||
mutatedProduct.value?.prices?.find((x) => x.currency_code === 'USD') ??
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
const clientSecret = ref()
|
||||
const paymentIntentId = ref()
|
||||
@@ -416,6 +626,39 @@ const confirmationToken = ref()
|
||||
const tax = ref()
|
||||
const total = ref()
|
||||
|
||||
const serverName = ref(props.serverName || '')
|
||||
const serverLoader = ref('Vanilla')
|
||||
const eulaAccepted = ref(false)
|
||||
|
||||
const mutatedProduct = ref({ ...props.product })
|
||||
const customMatchingProduct = ref()
|
||||
|
||||
const customServerConfig = reactive({
|
||||
ramInGb: 4,
|
||||
storageGbFormatted: computed(() => `${mutatedProduct.value.metadata.storage / 1024} GB`),
|
||||
ram: computed(() => customServerConfig.ramInGb * 1024),
|
||||
})
|
||||
|
||||
const updateCustomServerProduct = () => {
|
||||
if (props.customServer) {
|
||||
customMatchingProduct.value = props.product.find(
|
||||
(product) => product.metadata.ram === customServerConfig.ram,
|
||||
)
|
||||
|
||||
if (customMatchingProduct.value) mutatedProduct.value = { ...customMatchingProduct.value }
|
||||
}
|
||||
}
|
||||
|
||||
if (props.customServer) {
|
||||
updateCustomServerProduct()
|
||||
watch(
|
||||
() => customServerConfig.ram,
|
||||
() => {
|
||||
updateCustomServerProduct()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const selectedPaymentMethod = ref()
|
||||
const inputtedPaymentMethod = ref()
|
||||
const selectablePaymentMethods = computed(() => {
|
||||
@@ -433,7 +676,52 @@ const selectablePaymentMethods = computed(() => {
|
||||
return values
|
||||
})
|
||||
|
||||
const paymentLoading = ref(false)
|
||||
const primaryPaymentMethodId = computed(() => {
|
||||
if (
|
||||
props.customer &&
|
||||
props.customer.invoice_settings &&
|
||||
props.customer.invoice_settings.default_payment_method
|
||||
) {
|
||||
return props.customer.invoice_settings.default_payment_method
|
||||
} else if (props.paymentMethods && props.paymentMethods[0] && props.paymentMethods[0].id) {
|
||||
return props.paymentMethods[0].id
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const metadata = computed(() => {
|
||||
if (mutatedProduct.value.metadata.type === 'pyro') {
|
||||
return {
|
||||
type: 'pyro',
|
||||
server_name: serverName.value,
|
||||
source:
|
||||
props.projectId && props.versionId
|
||||
? {
|
||||
project_id: props.projectId,
|
||||
version_id: props.versionId,
|
||||
}
|
||||
: {
|
||||
loader: serverLoader.value,
|
||||
loader_version: '',
|
||||
game_version: 'latest',
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
function nextStep() {
|
||||
if (
|
||||
mutatedProduct.value.metadata.type === 'pyro' &&
|
||||
!props.projectId &&
|
||||
purchaseModalStep.value === 0
|
||||
) {
|
||||
purchaseModalStep.value = 1
|
||||
} else {
|
||||
beginPurchaseFlow(true)
|
||||
}
|
||||
}
|
||||
|
||||
async function beginPurchaseFlow(skip = false) {
|
||||
if (!props.customer) {
|
||||
@@ -446,11 +734,13 @@ async function beginPurchaseFlow(skip = false) {
|
||||
paymentLoading.value = true
|
||||
await refreshPayment(null, primaryPaymentMethodId.value)
|
||||
paymentLoading.value = false
|
||||
purchaseModalStep.value = 2
|
||||
purchaseModalStep.value =
|
||||
mutatedProduct.value.metadata.type === 'pyro' && !props.projectId ? 3 : 2
|
||||
} else {
|
||||
try {
|
||||
loadingPaymentMethodModal.value = 0
|
||||
purchaseModalStep.value = 1
|
||||
purchaseModalStep.value =
|
||||
mutatedProduct.value.metadata.type === 'pyro' && !props.projectId ? 2 : 1
|
||||
|
||||
await nextTick()
|
||||
|
||||
@@ -486,7 +776,6 @@ async function createConfirmationToken() {
|
||||
|
||||
if (error) {
|
||||
props.onError(error)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -509,7 +798,8 @@ async function validatePayment() {
|
||||
|
||||
loadingPaymentMethodModal.value = 0
|
||||
confirmationToken.value = await createConfirmationToken()
|
||||
purchaseModalStep.value = 2
|
||||
purchaseModalStep.value =
|
||||
mutatedProduct.value.metadata.type === 'pyro' && !props.projectId ? 3 : 2
|
||||
paymentLoading.value = false
|
||||
}
|
||||
|
||||
@@ -542,10 +832,11 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
||||
const result = await props.sendBillingRequest({
|
||||
charge: {
|
||||
type: 'new',
|
||||
product_id: props.product.id,
|
||||
product_id: mutatedProduct.value.id,
|
||||
interval: selectedPlan.value,
|
||||
},
|
||||
existing_payment_intent: paymentIntentId.value,
|
||||
metadata: metadata.value,
|
||||
...base,
|
||||
})
|
||||
|
||||
@@ -554,7 +845,7 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
||||
clientSecret.value = result.client_secret
|
||||
}
|
||||
|
||||
price.value = props.product.prices.find((x) => x.id === result.price_id)
|
||||
price.value = mutatedProduct.value.prices.find((x) => x.id === result.price_id)
|
||||
currency.value = price.value.currency_code
|
||||
tax.value = result.tax
|
||||
total.value = result.total
|
||||
@@ -585,5 +876,22 @@ async function submitPayment() {
|
||||
}
|
||||
paymentLoading.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
stripe = Stripe(props.publishableKey)
|
||||
|
||||
selectedPlan.value = 'yearly'
|
||||
serverName.value = props.serverName || ''
|
||||
serverLoader.value = 'Vanilla'
|
||||
|
||||
purchaseModalStep.value = 0
|
||||
loadingPaymentMethodModal.value = 0
|
||||
paymentLoading.value = false
|
||||
|
||||
purchaseModal.value.show()
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -148,8 +148,9 @@ defineExpose({
|
||||
position: fixed;
|
||||
box-shadow: var(--shadow-raised), var(--shadow-inset);
|
||||
border-radius: var(--radius-lg);
|
||||
background-color: var(--color-raised-bg);
|
||||
max-height: calc(100% - 2 * var(--gap-lg));
|
||||
overflow-y: auto;
|
||||
overflow-y: visible;
|
||||
width: 600px;
|
||||
pointer-events: auto;
|
||||
|
||||
@@ -166,10 +167,6 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: var(--color-raised-bg);
|
||||
}
|
||||
|
||||
transform: translateY(50vh);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
@@ -59,6 +59,7 @@ const props = withDefaults(
|
||||
warnOnClose?: boolean
|
||||
header?: string
|
||||
onHide?: () => void
|
||||
onShow?: () => void
|
||||
}>(),
|
||||
{
|
||||
type: true,
|
||||
@@ -67,14 +68,29 @@ const props = withDefaults(
|
||||
closeOnEsc: true,
|
||||
warnOnClose: false,
|
||||
onHide: () => {},
|
||||
onShow: () => {},
|
||||
},
|
||||
)
|
||||
|
||||
const open = ref(false)
|
||||
const visible = ref(false)
|
||||
|
||||
// make modal opening not shift page when there's a vertical scrollbar
|
||||
function addBodyPadding() {
|
||||
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth
|
||||
if (scrollBarWidth > 0) {
|
||||
document.body.style.paddingRight = `${scrollBarWidth}px`
|
||||
} else {
|
||||
document.body.style.paddingRight = ''
|
||||
}
|
||||
}
|
||||
|
||||
function show(event?: MouseEvent) {
|
||||
props.onShow()
|
||||
open.value = true
|
||||
|
||||
addBodyPadding()
|
||||
document.body.style.overflow = 'hidden'
|
||||
window.addEventListener('mousedown', updateMousePosition)
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
if (event) {
|
||||
@@ -91,6 +107,8 @@ function show(event?: MouseEvent) {
|
||||
function hide() {
|
||||
props.onHide()
|
||||
visible.value = false
|
||||
document.body.style.overflow = ''
|
||||
document.body.style.paddingRight = ''
|
||||
window.removeEventListener('mousedown', updateMousePosition)
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
setTimeout(() => {
|
||||
@@ -143,12 +161,6 @@ function handleKeyDown(event: KeyboardEvent) {
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-out;
|
||||
background: linear-gradient(to bottom, rgba(29, 48, 43, 0.52) 0%, rgba(14, 21, 26, 0.95) 100%);
|
||||
//transform: translate(
|
||||
// calc((-50vw + var(--_mouse-x, 50vw) * 1px) / 2),
|
||||
// calc((-50vh + var(--_mouse-y, 50vh) * 1px) / 2)
|
||||
// )
|
||||
// scaleX(0.8) scaleY(0.5);
|
||||
border-radius: 180px;
|
||||
filter: blur(5px);
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
@@ -159,8 +171,6 @@ function handleKeyDown(event: KeyboardEvent) {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
backdrop-filter: blur(5px);
|
||||
transform: translate(0, 0) scaleX(1) scaleY(1);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
&.noblur {
|
||||
|
||||
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}`[]
|
||||
|
||||
|
||||