Affiliates frontend (#4380)
* Begin affiliates frontend * Significant work on hooking up affiliates ui * Clean up server nodes menu * affiliates work * update affiliate time * oops * fix local import * fix local import x2 * remove line in dashboard * lint
1
packages/assets/external/facebook.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>Facebook</title><path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"/></svg>
|
||||
|
After Width: | Height: | Size: 563 B |
1
packages/assets/external/instagram.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>Instagram</title><path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
3
packages/assets/external/reels.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 122.14 122.88" fill="currentColor">
|
||||
<path d="M35.14 0H87c9.65 0 18.43 3.96 24.8 10.32 6.38 6.37 10.34 15.16 10.34 24.82v52.61c0 9.64-3.96 18.42-10.32 24.79l-.02.02c-6.38 6.37-15.16 10.32-24.79 10.32H35.14c-9.66 0-18.45-3.96-24.82-10.32l-.24-.27C3.86 105.95 0 97.27 0 87.74v-52.6c0-9.67 3.95-18.45 10.32-24.82S25.47 0 35.14 0zm56.37 31.02.07.11h21.6c-.87-5.68-3.58-10.78-7.48-14.69-4.8-4.8-11.42-7.78-18.7-7.78h-8.87l13.38 22.36zm-9.99.11L68.07 8.66h-29.5l13.61 22.47h29.34zm-39.41 0L28.95 9.39a26.446 26.446 0 0 0-12.51 7.05c-3.9 3.9-6.6 9.01-7.48 14.69h33.15zm71.37 8.66H8.66v47.96c0 7.17 2.89 13.7 7.56 18.48l.22.21c4.8 4.8 11.43 7.79 18.7 7.79H87c7.28 0 13.9-2.98 18.69-7.77l.02-.02c4.79-4.79 7.77-11.41 7.77-18.69V39.79zM50.95 54.95 77.78 72.4c.43.28.82.64 1.13 1.08a3.9 3.9 0 0 1-1 5.42L51.19 94.67c-.67.55-1.53.88-2.48.88a3.91 3.91 0 0 1-3.91-3.91V58.15h.02a3.902 3.902 0 0 1 6.13-3.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 978 B |
1
packages/assets/external/snapchat.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>Snapchat</title><path d="M12.206.793c.99 0 4.347.276 5.93 3.821.529 1.193.403 3.219.299 4.847l-.003.06c-.012.18-.022.345-.03.51.075.045.203.09.401.09.3-.016.659-.12 1.033-.301.165-.088.344-.104.464-.104.182 0 .359.029.509.09.45.149.734.479.734.838.015.449-.39.839-1.213 1.168-.089.029-.209.075-.344.119-.45.135-1.139.36-1.333.81-.09.224-.061.524.12.868l.015.015c.06.136 1.526 3.475 4.791 4.014.255.044.435.27.42.509 0 .075-.015.149-.045.225-.24.569-1.273.988-3.146 1.271-.059.091-.12.375-.164.57-.029.179-.074.36-.134.553-.076.271-.27.405-.555.405h-.03c-.135 0-.313-.031-.538-.074-.36-.075-.765-.135-1.273-.135-.3 0-.599.015-.913.074-.6.104-1.123.464-1.723.884-.853.599-1.826 1.288-3.294 1.288-.06 0-.119-.015-.18-.015h-.149c-1.468 0-2.427-.675-3.279-1.288-.599-.42-1.107-.779-1.707-.884-.314-.045-.629-.074-.928-.074-.54 0-.958.089-1.272.149-.211.043-.391.074-.54.074-.374 0-.523-.224-.583-.42-.061-.192-.09-.389-.135-.567-.046-.181-.105-.494-.166-.57-1.918-.222-2.95-.642-3.189-1.226-.031-.063-.052-.15-.055-.225-.015-.243.165-.465.42-.509 3.264-.54 4.73-3.879 4.791-4.02l.016-.029c.18-.345.224-.645.119-.869-.195-.434-.884-.658-1.332-.809-.121-.029-.24-.074-.346-.119-1.107-.435-1.257-.93-1.197-1.273.09-.479.674-.793 1.168-.793.146 0 .27.029.383.074.42.194.789.3 1.104.3.234 0 .384-.06.465-.105l-.046-.569c-.098-1.626-.225-3.651.307-4.837C7.392 1.077 10.739.807 11.727.807l.419-.015h.06z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
packages/assets/external/threads.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>Threads</title><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
packages/assets/external/tiktok.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>TikTok</title><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg>
|
||||
|
After Width: | Height: | Size: 728 B |
1
packages/assets/external/twitch.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>Twitch</title><path d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z"/></svg>
|
||||
|
After Width: | Height: | Size: 313 B |
1
packages/assets/external/youtubegaming.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>YouTube Gaming</title><path d="M24 13.2v-6l-6-3.6-6 3.6-6-3.6-6 3.6v6l12 7.2zM8.4 10.8H6v2.4H4.8v-2.4H2.4V9.6h2.4V7.2H6v2.4h2.4zm7.2 2.4a1.2 1.2 0 01-1.2-1.2c0-.66.54-1.2 1.2-1.2.66 0 1.2.54 1.2 1.2 0 .66-.54 1.2-1.2 1.2zm3.6-2.4A1.2 1.2 0 0118 9.6c0-.66.54-1.2 1.2-1.2.66 0 1.2.54 1.2 1.2 0 .66-.54 1.2-1.2 1.2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 420 B |
1
packages/assets/external/youtubeshorts.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><title>YouTube Shorts</title><path d="m18.931 9.99-1.441-.601 1.717-.913a4.48 4.48 0 0 0 1.874-6.078 4.506 4.506 0 0 0-6.09-1.874L4.792 5.929a4.504 4.504 0 0 0-2.402 4.193 4.521 4.521 0 0 0 2.666 3.904c.036.012 1.442.6 1.442.6l-1.706.901a4.51 4.51 0 0 0-2.369 3.967A4.528 4.528 0 0 0 6.93 24c.725 0 1.437-.174 2.08-.508l10.21-5.406a4.494 4.494 0 0 0 2.39-4.192 4.525 4.525 0 0 0-2.678-3.904ZM9.597 15.19V8.824l6.007 3.184z"/></svg>
|
||||
|
After Width: | Height: | Size: 523 B |
@@ -1,6 +1,7 @@
|
||||
// Auto-generated icon imports and exports
|
||||
// Do not edit this file manually - run 'pnpm run fix' to regenerate
|
||||
|
||||
import _AffiliateIcon from './icons/affiliate.svg?component'
|
||||
import _AlignLeftIcon from './icons/align-left.svg?component'
|
||||
import _ArchiveIcon from './icons/archive.svg?component'
|
||||
import _ArrowBigRightDashIcon from './icons/arrow-big-right-dash.svg?component'
|
||||
@@ -30,6 +31,7 @@ import _CheckCheckIcon from './icons/check-check.svg?component'
|
||||
import _CheckCircleIcon from './icons/check-circle.svg?component'
|
||||
import _ChevronLeftIcon from './icons/chevron-left.svg?component'
|
||||
import _ChevronRightIcon from './icons/chevron-right.svg?component'
|
||||
import _CircleUserIcon from './icons/circle-user.svg?component'
|
||||
import _ClearIcon from './icons/clear.svg?component'
|
||||
import _ClientIcon from './icons/client.svg?component'
|
||||
import _ClipboardCopyIcon from './icons/clipboard-copy.svg?component'
|
||||
@@ -190,6 +192,7 @@ import _UploadIcon from './icons/upload.svg?component'
|
||||
import _UserIcon from './icons/user.svg?component'
|
||||
import _UserCogIcon from './icons/user-cog.svg?component'
|
||||
import _UserPlusIcon from './icons/user-plus.svg?component'
|
||||
import _UserSearchIcon from './icons/user-search.svg?component'
|
||||
import _UserXIcon from './icons/user-x.svg?component'
|
||||
import _UsersIcon from './icons/users.svg?component'
|
||||
import _VersionIcon from './icons/version.svg?component'
|
||||
@@ -202,6 +205,7 @@ import _XCircleIcon from './icons/x-circle.svg?component'
|
||||
import _ZoomInIcon from './icons/zoom-in.svg?component'
|
||||
import _ZoomOutIcon from './icons/zoom-out.svg?component'
|
||||
|
||||
export const AffiliateIcon = _AffiliateIcon
|
||||
export const AlignLeftIcon = _AlignLeftIcon
|
||||
export const ArchiveIcon = _ArchiveIcon
|
||||
export const ArrowBigRightDashIcon = _ArrowBigRightDashIcon
|
||||
@@ -231,6 +235,7 @@ export const CheckCircleIcon = _CheckCircleIcon
|
||||
export const CheckIcon = _CheckIcon
|
||||
export const ChevronLeftIcon = _ChevronLeftIcon
|
||||
export const ChevronRightIcon = _ChevronRightIcon
|
||||
export const CircleUserIcon = _CircleUserIcon
|
||||
export const ClearIcon = _ClearIcon
|
||||
export const ClientIcon = _ClientIcon
|
||||
export const ClipboardCopyIcon = _ClipboardCopyIcon
|
||||
@@ -390,6 +395,7 @@ export const UpdatedIcon = _UpdatedIcon
|
||||
export const UploadIcon = _UploadIcon
|
||||
export const UserCogIcon = _UserCogIcon
|
||||
export const UserPlusIcon = _UserPlusIcon
|
||||
export const UserSearchIcon = _UserSearchIcon
|
||||
export const UserXIcon = _UserXIcon
|
||||
export const UserIcon = _UserIcon
|
||||
export const UsersIcon = _UsersIcon
|
||||
|
||||
11
packages/assets/icons/affiliate.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="7"/>
|
||||
<circle cx="12" cy="10.6" r="2.1"/>
|
||||
<circle cx="3.7" cy="3.7" r="1.8"/>
|
||||
<circle cx="20.3" cy="3.7" r="1.8"/>
|
||||
<circle cx="3.7" cy="20.3" r="1.8"/>
|
||||
<circle cx="20.3" cy="20.3" r="1.8"/>
|
||||
<path d="M8.5 18.1v-1.2c0-.8.6-1.4 1.4-1.4h4.2c.8 0 1.4.6 1.4 1.4v1.2M17 7l2-2M5 19l2-2M17 17l2 2M5 5l2 2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 532 B |
1
packages/assets/icons/circle-user.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-circle-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg>
|
||||
|
After Width: | Height: | Size: 368 B |
1
packages/assets/icons/user-search.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-user-search-icon lucide-user-search"><circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/></svg>
|
||||
|
After Width: | Height: | Size: 370 B |
@@ -23,29 +23,38 @@ import _SleepingRinthbot from './branding/rinthbot/sleeping.webp'
|
||||
import _SobbingRinthbot from './branding/rinthbot/sobbing.webp'
|
||||
import _ThinkingRinthbot from './branding/rinthbot/thinking.webp'
|
||||
import _WavingRinthbot from './branding/rinthbot/waving.webp'
|
||||
// External Icons
|
||||
import _AppleIcon from './external/apple.svg?component'
|
||||
import _BlueskyIcon from './external/bluesky.svg?component'
|
||||
import _BuyMeACoffeeIcon from './external/bmac.svg?component'
|
||||
import _CurseForgeIcon from './external/curseforge.svg?component'
|
||||
import _DiscordIcon from './external/discord.svg?component'
|
||||
import _FacebookIcon from './external/facebook.svg?component'
|
||||
import _GithubIcon from './external/github.svg?component'
|
||||
import _InstagramIcon from './external/instagram.svg?component'
|
||||
import _KoFiIcon from './external/kofi.svg?component'
|
||||
import _MastodonIcon from './external/mastodon.svg?component'
|
||||
import _OpenCollectiveIcon from './external/opencollective.svg?component'
|
||||
import _PatreonIcon from './external/patreon.svg?component'
|
||||
import _PayPalIcon from './external/paypal.svg?component'
|
||||
import _RedditIcon from './external/reddit.svg?component'
|
||||
// External Icons
|
||||
import _ReelsIcon from './external/reels.svg?component'
|
||||
import _SnapchatIcon from './external/snapchat.svg?component'
|
||||
import _SSODiscordIcon from './external/sso/discord.svg?component'
|
||||
import _SSOGitHubIcon from './external/sso/github.svg?component'
|
||||
import _SSOGitLabIcon from './external/sso/gitlab.svg?component'
|
||||
import _SSOGoogleIcon from './external/sso/google.svg?component'
|
||||
import _SSOMicrosoftIcon from './external/sso/microsoft.svg?component'
|
||||
import _SSOSteamIcon from './external/sso/steam.svg?component'
|
||||
import _ThreadsIcon from './external/threads.svg?component'
|
||||
import _TikTokIcon from './external/tiktok.svg?component'
|
||||
import _TumblrIcon from './external/tumblr.svg?component'
|
||||
import _TwitchIcon from './external/twitch.svg?component'
|
||||
import _TwitterIcon from './external/twitter.svg?component'
|
||||
import _WindowsIcon from './external/windows.svg?component'
|
||||
import _YouTubeIcon from './external/youtube.svg?component'
|
||||
import _YouTubeGaming from './external/youtubegaming.svg?component'
|
||||
import _YouTubeShortsIcon from './external/youtubeshorts.svg?component'
|
||||
|
||||
export const ModrinthIcon = _ModrinthIcon
|
||||
export const BrowserWindowSuccessIllustration = _BrowserWindowSuccessIllustration
|
||||
@@ -73,6 +82,13 @@ export const BuyMeACoffeeIcon = _BuyMeACoffeeIcon
|
||||
export const GithubIcon = _GithubIcon
|
||||
export const CurseForgeIcon = _CurseForgeIcon
|
||||
export const DiscordIcon = _DiscordIcon
|
||||
export const FacebookIcon = _FacebookIcon
|
||||
export const InstagramIcon = _InstagramIcon
|
||||
export const SnapchatIcon = _SnapchatIcon
|
||||
export const ReelsIcon = _ReelsIcon
|
||||
export const TikTokIcon = _TikTokIcon
|
||||
export const TwitchIcon = _TwitchIcon
|
||||
export const ThreadsIcon = _ThreadsIcon
|
||||
export const KoFiIcon = _KoFiIcon
|
||||
export const MastodonIcon = _MastodonIcon
|
||||
export const OpenCollectiveIcon = _OpenCollectiveIcon
|
||||
@@ -83,6 +99,8 @@ export const TumblrIcon = _TumblrIcon
|
||||
export const TwitterIcon = _TwitterIcon
|
||||
export const WindowsIcon = _WindowsIcon
|
||||
export const YouTubeIcon = _YouTubeIcon
|
||||
export const YouTubeGaming = _YouTubeGaming
|
||||
export const YouTubeShortsIcon = _YouTubeShortsIcon
|
||||
|
||||
export * from './generated-icons'
|
||||
export { default as ClassicPlayerModel } from './models/classic-player.gltf?url'
|
||||
|
||||
72
packages/ui/src/components/affiliate/AffiliateLinkCard.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="card-shadow flex flex-col gap-4 rounded-2xl bg-bg-raised p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="flex items-center justify-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg p-2"
|
||||
>
|
||||
<AutoBrandIcon :keyword="affiliate.source_name" class="h-6 w-6">
|
||||
<AffiliateIcon />
|
||||
</AutoBrandIcon>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="w-fit text-lg font-bold text-contrast">
|
||||
{{ affiliate.source_name }}
|
||||
</span>
|
||||
<span v-if="createdBy" class="text-sm text-secondary">
|
||||
{{ formatMessage(messages.createdBy, { user: createdBy }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<slot />
|
||||
<ButtonStyled v-if="showRevoke" color="red" color-fill="text">
|
||||
<button @click="emit('revoke', affiliate)">
|
||||
<XCircleIcon />
|
||||
{{ formatMessage(messages.revokeAffiliateLink) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<CopyCode :text="`https://modrinth.gg?afl=${affiliate.id}`" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AffiliateIcon, XCircleIcon } from '@modrinth/assets'
|
||||
import type { AffiliateLink } from '@modrinth/utils'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
|
||||
import { AutoBrandIcon, ButtonStyled, CopyCode } from '../index.ts'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
affiliate: AffiliateLink
|
||||
showRevoke?: boolean
|
||||
createdBy?: string
|
||||
}>(),
|
||||
{
|
||||
showRevoke: true,
|
||||
createdBy: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'revoke', affiliate: AffiliateLink): void
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
viewAnalytics: {
|
||||
id: 'affiliate.viewAnalytics',
|
||||
defaultMessage: 'View analytics',
|
||||
},
|
||||
revokeAffiliateLink: {
|
||||
id: 'affiliate.revoke',
|
||||
defaultMessage: 'Revoke affiliate link',
|
||||
},
|
||||
createdBy: {
|
||||
id: 'affiliate.createdBy',
|
||||
defaultMessage: 'Created by {user}',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<NewModal ref="modal" :header="formatMessage(messages.createHeader)">
|
||||
<div class="flex flex-col">
|
||||
<label v-if="showUserField" class="contents" for="create-affiliate-user-input">
|
||||
<span class="text-lg font-semibold text-contrast mb-1">
|
||||
{{ formatMessage(messages.createUserLabel) }}
|
||||
</span>
|
||||
<span class="text-secondary mb-2">{{ formatMessage(messages.createUserDescription) }}</span>
|
||||
</label>
|
||||
<div v-if="showUserField" class="mb-4">
|
||||
<div class="iconified-input">
|
||||
<UserIcon aria-hidden="true" />
|
||||
<input
|
||||
id="create-affiliate-user-input"
|
||||
v-model="affiliateUsername"
|
||||
class="card-shadow"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
type="text"
|
||||
:placeholder="formatMessage(messages.createUserPlaceholder)"
|
||||
/>
|
||||
<Button v-if="affiliateUsername" class="r-btn" @click="() => (affiliateUsername = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="contents" for="create-affiliate-title-input">
|
||||
<span class="text-lg font-semibold text-contrast mb-1">
|
||||
{{ formatMessage(messages.createTitleLabel) }}
|
||||
</span>
|
||||
<span class="text-secondary mb-2">{{
|
||||
formatMessage(messages.createTitleDescription)
|
||||
}}</span>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="iconified-input">
|
||||
<AutoBrandIcon :keyword="affiliateLinkTitle" aria-hidden="true">
|
||||
<AffiliateIcon />
|
||||
</AutoBrandIcon>
|
||||
<input
|
||||
id="create-affiliate-title-input"
|
||||
v-model="affiliateLinkTitle"
|
||||
class="card-shadow"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
type="text"
|
||||
:placeholder="formatMessage(messages.createTitlePlaceholder)"
|
||||
/>
|
||||
<Button v-if="affiliateLinkTitle" class="r-btn" @click="() => (affiliateLinkTitle = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="creatingLink || !canCreate" @click="createAffiliateLink">
|
||||
<SpinnerIcon v-if="creatingLink" class="animate-spin" />
|
||||
<PlusIcon v-else />
|
||||
{{ formatMessage(creatingLink ? messages.creatingButton : messages.createButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
<script lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { AffiliateIcon, PlusIcon, SpinnerIcon, UserIcon, XIcon } from '@modrinth/assets'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
|
||||
import { AutoBrandIcon, Button, ButtonStyled, NewModal } from '../index.ts'
|
||||
export type CreateAffiliateProps = { sourceName: string; username?: string }
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
showUserField?: boolean
|
||||
creatingLink?: boolean
|
||||
}>(),
|
||||
{
|
||||
showUserField: false,
|
||||
creatingLink: false,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'create', data: CreateAffiliateProps): void
|
||||
}>()
|
||||
|
||||
const modal = useTemplateRef<typeof NewModal>('modal')
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const affiliateLinkTitle = ref('')
|
||||
const affiliateUsername = ref('')
|
||||
|
||||
const canCreate = computed(() => {
|
||||
if (props.showUserField) {
|
||||
return affiliateLinkTitle.value.trim() && affiliateUsername.value.trim()
|
||||
}
|
||||
return affiliateLinkTitle.value.trim()
|
||||
})
|
||||
|
||||
function createAffiliateLink() {
|
||||
if (!canCreate.value) {
|
||||
return
|
||||
}
|
||||
|
||||
emit('create', {
|
||||
sourceName: affiliateLinkTitle.value,
|
||||
username: props.showUserField ? affiliateUsername.value : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
function close() {
|
||||
modal.value?.hide()
|
||||
affiliateLinkTitle.value = ''
|
||||
affiliateUsername.value = ''
|
||||
}
|
||||
|
||||
function show() {
|
||||
modal.value?.show()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
close,
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
createHeader: {
|
||||
id: 'affiliate.create.header',
|
||||
defaultMessage: 'Creating new affiliate code',
|
||||
},
|
||||
createTitleLabel: {
|
||||
id: 'affiliate.create.title.label',
|
||||
defaultMessage: 'Title of affiliate link',
|
||||
},
|
||||
createTitleDescription: {
|
||||
id: 'affiliate.create.title.description',
|
||||
defaultMessage: 'Give your affiliate link a name so you know where people are coming from!',
|
||||
},
|
||||
createTitlePlaceholder: {
|
||||
id: 'affiliate.create.title.placeholder',
|
||||
defaultMessage: 'e.g. YouTube',
|
||||
},
|
||||
createUserLabel: {
|
||||
id: 'affiliate.create.user.label',
|
||||
defaultMessage: 'Username',
|
||||
},
|
||||
createUserDescription: {
|
||||
id: 'affiliate.create.user.description',
|
||||
defaultMessage: 'The username of the user to create the affiliate code for',
|
||||
},
|
||||
createUserPlaceholder: {
|
||||
id: 'affiliate.create.user.placeholder',
|
||||
defaultMessage: 'Enter username...',
|
||||
},
|
||||
createButton: {
|
||||
id: 'affiliate.create.button',
|
||||
defaultMessage: 'Create affiliate link',
|
||||
},
|
||||
creatingButton: {
|
||||
id: 'affiliate.creating.button',
|
||||
defaultMessage: 'Creating affiliate link...',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
153
packages/ui/src/components/base/AutoBrandIcon.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AppleIcon,
|
||||
BlueskyIcon,
|
||||
BuyMeACoffeeIcon,
|
||||
CurseForgeIcon,
|
||||
DiscordIcon,
|
||||
FacebookIcon,
|
||||
GithubIcon,
|
||||
InstagramIcon,
|
||||
KoFiIcon,
|
||||
MastodonIcon,
|
||||
ModrinthIcon,
|
||||
OpenCollectiveIcon,
|
||||
PatreonIcon,
|
||||
PayPalIcon,
|
||||
RedditIcon,
|
||||
ReelsIcon,
|
||||
SnapchatIcon,
|
||||
ThreadsIcon,
|
||||
TikTokIcon,
|
||||
TumblrIcon,
|
||||
TwitchIcon,
|
||||
TwitterIcon,
|
||||
WindowsIcon,
|
||||
YouTubeGaming,
|
||||
YouTubeIcon,
|
||||
YouTubeShortsIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
keyword: string
|
||||
}>()
|
||||
|
||||
const services = [
|
||||
{
|
||||
icon: AppleIcon,
|
||||
keywords: ['apple'],
|
||||
},
|
||||
{
|
||||
icon: BlueskyIcon,
|
||||
keywords: ['bluesky', 'bsky', 'blue sky'],
|
||||
},
|
||||
{
|
||||
icon: BuyMeACoffeeIcon,
|
||||
keywords: ['buymeacoffee', 'bmac', 'buy me a coffee'],
|
||||
},
|
||||
{
|
||||
icon: DiscordIcon,
|
||||
keywords: ['discord'],
|
||||
},
|
||||
{
|
||||
icon: FacebookIcon,
|
||||
keywords: ['facebook', 'fb', 'face book'],
|
||||
},
|
||||
{
|
||||
icon: GithubIcon,
|
||||
keywords: ['github', 'gh', 'git hub'],
|
||||
},
|
||||
{
|
||||
icon: ThreadsIcon,
|
||||
keywords: ['threads'],
|
||||
},
|
||||
{
|
||||
icon: InstagramIcon,
|
||||
keywords: ['instagram', 'ig', 'insta'],
|
||||
},
|
||||
{
|
||||
icon: KoFiIcon,
|
||||
keywords: ['ko-fi', 'kofi', 'ko fi'],
|
||||
},
|
||||
{
|
||||
icon: MastodonIcon,
|
||||
keywords: ['mastodon'],
|
||||
},
|
||||
{
|
||||
icon: OpenCollectiveIcon,
|
||||
keywords: ['opencollective', 'open collective'],
|
||||
},
|
||||
{
|
||||
icon: PatreonIcon,
|
||||
keywords: ['patreon'],
|
||||
},
|
||||
{
|
||||
icon: PayPalIcon,
|
||||
keywords: ['paypal', 'pay pal'],
|
||||
},
|
||||
{
|
||||
icon: RedditIcon,
|
||||
keywords: ['reddit'],
|
||||
},
|
||||
{
|
||||
icon: ReelsIcon,
|
||||
keywords: ['reels', 'instagram reels', 'facebook reels'],
|
||||
},
|
||||
{
|
||||
icon: SnapchatIcon,
|
||||
keywords: ['snapchat'],
|
||||
},
|
||||
{
|
||||
icon: TikTokIcon,
|
||||
keywords: ['tiktok', 'tik', 'tok'],
|
||||
},
|
||||
{
|
||||
icon: TumblrIcon,
|
||||
keywords: ['tumblr'],
|
||||
},
|
||||
{
|
||||
icon: TwitchIcon,
|
||||
keywords: ['twitch', 'twitch.tv'],
|
||||
},
|
||||
{
|
||||
icon: WindowsIcon,
|
||||
keywords: ['windows', 'microsoft'],
|
||||
},
|
||||
{
|
||||
icon: YouTubeIcon,
|
||||
keywords: ['youtube', 'yt'],
|
||||
},
|
||||
{
|
||||
icon: YouTubeShortsIcon,
|
||||
keywords: ['shorts', 'youtube shorts'],
|
||||
},
|
||||
{
|
||||
icon: YouTubeGaming,
|
||||
keywords: ['youtube gaming'],
|
||||
},
|
||||
{
|
||||
icon: CurseForgeIcon,
|
||||
keywords: ['curseforge', 'cf', 'curse', 'curse forge'],
|
||||
},
|
||||
{
|
||||
icon: ModrinthIcon,
|
||||
keywords: ['modrinth', 'mod rinth', 'modrith', 'mr'],
|
||||
},
|
||||
{
|
||||
icon: TwitterIcon,
|
||||
keywords: ['twitter', 'x.com', 'x'],
|
||||
},
|
||||
]
|
||||
|
||||
const selectedService = computed(() =>
|
||||
services.find((service) =>
|
||||
service.keywords.some((keyword) => props.keyword.toLowerCase().includes(keyword)),
|
||||
),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="selectedService?.icon" v-if="selectedService" />
|
||||
<slot v-else />
|
||||
</template>
|
||||
@@ -58,6 +58,7 @@ const props = defineProps<{
|
||||
) => Promise<UpdatePaymentIntentResponse | CreatePaymentIntentResponse | null>
|
||||
onError: (err: Error) => void
|
||||
onFinalizeNoPaymentChange?: () => Promise<void>
|
||||
affiliateCode?: string | null
|
||||
}>()
|
||||
|
||||
const modal = useTemplateRef<InstanceType<typeof NewModal>>('modal')
|
||||
@@ -66,6 +67,7 @@ const selectedInterval = ref<ServerBillingInterval>('quarterly')
|
||||
const loading = ref(false)
|
||||
const selectedRegion = ref<string>()
|
||||
const projectId = ref<string>()
|
||||
const affiliateCode = ref(props.affiliateCode ?? null)
|
||||
|
||||
const {
|
||||
initializeStripe,
|
||||
@@ -96,6 +98,7 @@ const {
|
||||
projectId,
|
||||
props.initiatePayment,
|
||||
props.onError,
|
||||
affiliateCode,
|
||||
)
|
||||
|
||||
const customServer = ref<boolean>(false)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
export { default as Accordion } from './base/Accordion.vue'
|
||||
export { default as Admonition } from './base/Admonition.vue'
|
||||
export { default as AppearingProgressBar } from './base/AppearingProgressBar.vue'
|
||||
export { default as AutoBrandIcon } from './base/AutoBrandIcon.vue'
|
||||
export { default as AutoLink } from './base/AutoLink.vue'
|
||||
export { default as Avatar } from './base/Avatar.vue'
|
||||
export { default as Badge } from './base/Badge.vue'
|
||||
@@ -98,6 +99,10 @@ export { default as SearchFilterControl } from './search/SearchFilterControl.vue
|
||||
export { default as SearchFilterOption } from './search/SearchFilterOption.vue'
|
||||
export { default as SearchSidebarFilter } from './search/SearchSidebarFilter.vue'
|
||||
|
||||
// Affiliate
|
||||
export { default as AffiliateLinkCard } from './affiliate/AffiliateLinkCard.vue'
|
||||
export { default as AffiliateLinkCreateModal } from './affiliate/AffiliateLinkCreateModal.vue'
|
||||
|
||||
// Billing
|
||||
export { default as AddPaymentMethodModal } from './billing/AddPaymentMethodModal.vue'
|
||||
export { default as ModrinthServersPurchaseModal } from './billing/ModrinthServersPurchaseModal.vue'
|
||||
|
||||
@@ -37,6 +37,7 @@ export const useStripe = (
|
||||
body: CreatePaymentIntentRequest | UpdatePaymentIntentRequest,
|
||||
) => Promise<CreatePaymentIntentResponse | UpdatePaymentIntentResponse | null>,
|
||||
onError: (err: Error) => void,
|
||||
affiliateCode?: Ref<string | null>,
|
||||
) => {
|
||||
const stripe = ref<StripeJs | null>(null)
|
||||
|
||||
@@ -229,6 +230,13 @@ export const useStripe = (
|
||||
|
||||
let result: BasePaymentIntentResponse | null = null
|
||||
|
||||
const affiliateMetadata =
|
||||
affiliateCode && affiliateCode.value
|
||||
? {
|
||||
affiliate_code: affiliateCode.value,
|
||||
}
|
||||
: {}
|
||||
|
||||
const metadata: CreatePaymentIntentRequest['metadata'] = {
|
||||
type: 'pyro',
|
||||
server_region: region.value,
|
||||
@@ -237,6 +245,7 @@ export const useStripe = (
|
||||
project_id: project.value,
|
||||
}
|
||||
: {},
|
||||
...affiliateMetadata,
|
||||
}
|
||||
|
||||
if (paymentIntentId.value) {
|
||||
|
||||
@@ -1,4 +1,40 @@
|
||||
{
|
||||
"affiliate.create.button": {
|
||||
"defaultMessage": "Create affiliate link"
|
||||
},
|
||||
"affiliate.create.header": {
|
||||
"defaultMessage": "Creating new affiliate code"
|
||||
},
|
||||
"affiliate.create.title.description": {
|
||||
"defaultMessage": "Give your affiliate link a name so you know where people are coming from!"
|
||||
},
|
||||
"affiliate.create.title.label": {
|
||||
"defaultMessage": "Title of affiliate link"
|
||||
},
|
||||
"affiliate.create.title.placeholder": {
|
||||
"defaultMessage": "e.g. YouTube"
|
||||
},
|
||||
"affiliate.create.user.description": {
|
||||
"defaultMessage": "The username of the user to create the affiliate code for"
|
||||
},
|
||||
"affiliate.create.user.label": {
|
||||
"defaultMessage": "Username"
|
||||
},
|
||||
"affiliate.create.user.placeholder": {
|
||||
"defaultMessage": "Enter username..."
|
||||
},
|
||||
"affiliate.createdBy": {
|
||||
"defaultMessage": "Created by {user}"
|
||||
},
|
||||
"affiliate.creating.button": {
|
||||
"defaultMessage": "Creating affiliate link..."
|
||||
},
|
||||
"affiliate.revoke": {
|
||||
"defaultMessage": "Revoke affiliate link"
|
||||
},
|
||||
"affiliate.viewAnalytics": {
|
||||
"defaultMessage": "View analytics"
|
||||
},
|
||||
"badge.beta": {
|
||||
"defaultMessage": "Beta"
|
||||
},
|
||||
@@ -8,6 +44,9 @@
|
||||
"badge.new": {
|
||||
"defaultMessage": "New"
|
||||
},
|
||||
"button.affiliate-links": {
|
||||
"defaultMessage": "Affiliate links"
|
||||
},
|
||||
"button.analytics": {
|
||||
"defaultMessage": "Analytics"
|
||||
},
|
||||
|
||||
@@ -79,6 +79,7 @@ export type CreatePaymentIntentRequest = PaymentRequestType & {
|
||||
type: 'pyro'
|
||||
server_name?: string
|
||||
server_region?: string
|
||||
affiliate_code?: string
|
||||
source:
|
||||
| {
|
||||
loader: Loaders
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { defineMessages } from '@vintl/vintl'
|
||||
|
||||
export const commonMessages = defineMessages({
|
||||
affiliateLinksButton: {
|
||||
id: 'button.affiliate-links',
|
||||
defaultMessage: 'Affiliate links',
|
||||
},
|
||||
analyticsButton: {
|
||||
id: 'button.analytics',
|
||||
defaultMessage: 'Analytics',
|
||||
|
||||
@@ -334,6 +334,7 @@ export enum UserBadge {
|
||||
ALPHA_TESTER = 1 << 4,
|
||||
CONTRIBUTOR = 1 << 5,
|
||||
TRANSLATOR = 1 << 6,
|
||||
AFFILIATE = 1 << 7,
|
||||
}
|
||||
|
||||
export type UserBadges = number
|
||||
@@ -597,3 +598,11 @@ export interface DelphiReport {
|
||||
status: 'pending' | 'approved' | 'rejected'
|
||||
content?: string
|
||||
}
|
||||
|
||||
export type AffiliateLink = {
|
||||
id: string
|
||||
created_at: string
|
||||
created_by: string
|
||||
affiliate: string
|
||||
source_name: string
|
||||
}
|
||||
|
||||