Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import InstallToPlayModal from '@/components/ui/modal/InstallToPlayModal.vue'
import ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
import NavButton from '@/components/ui/NavButton.vue'
import PrideFundraiserBanner from '@/components/ui/PrideFundraiserBanner.vue'
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
Expand Down Expand Up @@ -134,6 +135,10 @@ const route = useRoute()
const APP_LEFT_NAV_WIDTH = '4rem'
const APP_SIDEBAR_WIDTH = 300
const INTERCOM_BUBBLE_DEFAULT_PADDING = 20
const prideFundraiserData = {
goal_amount: 5000,
live_amount: 3250,
}
const credentials = ref()
const sidebarToggled = ref(true)
const unsubscribeSidebarToggle = themeStore.$subscribe(() => {
Expand Down Expand Up @@ -1479,6 +1484,12 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<FriendsList :credentials="credentials" :sign-in="() => signIn()" />
</suspense>
</div>
<div
v-if="themeStore.getFeatureFlag('pride_fundraiser')"
class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"
>
<PrideFundraiserBanner v-bind="prideFundraiserData" />
</div>
<div v-if="news && news.length > 0" class="p-4 flex flex-col items-center">
<h3 class="text-base mb-4 text-primary font-medium m-0 text-left w-full">News</h3>
<div class="space-y-4 flex flex-col items-center w-full">
Expand Down
88 changes: 88 additions & 0 deletions apps/app-frontend/src/components/ui/PrideFundraiserBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { CalendarIcon, ExternalIcon, UsersIcon } from '@modrinth/assets'
import { ProgressBar } from '@modrinth/ui'
import { openUrl } from '@tauri-apps/plugin-opener'
import { computed } from 'vue'

const props = defineProps<{
goal_amount: number
live_amount: number
}>()

const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0,
})

const formattedGoalAmount = computed(() => currencyFormatter.format(props.goal_amount))
const formattedLiveAmount = computed(() => currencyFormatter.format(props.live_amount))
const progressLabel = computed(
() => `${formattedLiveAmount.value} of ${formattedGoalAmount.value} raised`,
)

async function openPrideFundraiser() {
await openUrl('https://modrinth.com/pride')
}
</script>

<template>
<section
class="flex w-full flex-col gap-3 rounded-xl border border-solid border-surface-5 bg-button-bg p-3 text-primary"
>
<div class="flex w-full items-center justify-between gap-2">
<h2 class="m-0 min-w-0 truncate text-base font-semibold text-contrast">
Pride Fundraiser 2026
</h2>
<button
type="button"
class="m-0 flex size-5 shrink-0 cursor-pointer items-center justify-center border-0 bg-transparent p-0 text-primary transition-colors hover:text-contrast focus-visible:text-contrast"
aria-label="Open Pride fundraiser"
@click="openPrideFundraiser"
>
<ExternalIcon aria-hidden="true" class="size-5" />
</button>
</div>
<div class="h-px w-full bg-surface-5" />
<div class="flex w-full flex-col gap-2.5">
<div class="flex items-end gap-1 whitespace-nowrap">
<span class="text-base font-semibold leading-5 text-contrast">
{{ formattedLiveAmount }}
</span>
<span class="text-xs font-medium leading-4">of {{ formattedGoalAmount }}</span>
</div>
<ProgressBar
class="pride-fundraiser-banner__progress"
:progress="live_amount"
:max="goal_amount"
color="purple"
full-width
:gradient-border="false"
:aria-label="progressLabel"
/>
<div class="flex flex-wrap items-center gap-2 text-xs font-medium leading-4">
<span class="flex items-center gap-1">
<UsersIcon aria-hidden="true" class="size-4 shrink-0" />
6767 supporters
</span>
<span class="flex items-center gap-1">
<CalendarIcon aria-hidden="true" class="size-4 shrink-0" />
6 days left
</span>
</div>
</div>
</section>
</template>

<style scoped>
.pride-fundraiser-banner__progress :deep(.progress-bar) {
background: linear-gradient(
90deg,
Comment thread
IMB11 marked this conversation as resolved.
var(--color-red) 0%,
var(--color-orange) 20%,
var(--color-green) 50%,
var(--color-blue) 75%,
var(--color-purple) 100%
);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,6 @@ const messages = defineMessages({
<FriendsSection
v-if="pendingFriends.length > 0"
:is-searching="!!search"
open-by-default
:friends="pendingFriends"
:heading="formatMessage(messages.pending)"
:remove-friend="removeFriend"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { DropdownIcon, EditIcon, PlusIcon, TrashIcon } from '@modrinth/assets'
import { DropdownIcon, EditIcon, PlusIcon, TrashIcon, UnknownIcon } from '@modrinth/assets'
import {
Accordion,
ButtonStyled,
Expand All @@ -11,6 +11,7 @@ import {
useVIntl,
} from '@modrinth/ui'
import { useElementSize, useWindowSize } from '@vueuse/core'
import { Tooltip } from 'floating-vue'
import { computed, nextTick, onUnmounted, ref, useTemplateRef, watch } from 'vue'

import type { RenderResult } from '@/helpers/rendering/batch-skin-renderer.ts'
Expand All @@ -24,13 +25,15 @@ type AddSkinButtonRef = SkinLikeTextButtonExpose | SkinLikeTextButtonExpose[]

interface DefaultSkinSection {
title: string
infoTooltip?: string
skins: Skin[]
}

interface SkinSection {
key: string
title: string
kind: SkinSectionKind
infoTooltip?: string
skins: Skin[]
}

Expand Down Expand Up @@ -145,6 +148,7 @@ const sections = computed<SkinSection[]>(() => [
key: defaultSkinSectionKey(section.title),
title: section.title,
kind: 'default' as const,
infoTooltip: section.infoTooltip,
skins: section.skins,
})),
])
Expand Down Expand Up @@ -330,6 +334,24 @@ defineExpose({ getAddSkinButtonElement })
<span class="min-w-0 text-xl font-semibold leading-7 text-primary">
{{ section.title }}
</span>
<Tooltip
v-if="section.infoTooltip"
theme="dismissable-prompt"
placement="top"
:triggers="['hover', 'focus']"
>
<span
class="inline-flex size-6 shrink-0 items-center justify-center text-secondary transition-colors group-hover:text-primary"
@click.stop
>
<UnknownIcon class="size-5" />
</span>
<template #popper>
<p class="m-0 max-w-96 text-wrap text-sm font-medium leading-tight">
{{ section.infoTooltip }}
</p>
</template>
</Tooltip>
</template>

<div
Expand Down
6 changes: 6 additions & 0 deletions apps/app-frontend/src/locales/en-US/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@
"app.skins.section.minecon-earth-2017": {
"message": "MINECON Earth 2017"
},
"app.skins.section.modrinth-pride": {
"message": "Modrinth Pride"
},
"app.skins.section.modrinth-pride.tooltip": {
"message": "You received these skins for donating to a Modrinth Pride fundraiser during Pride Month."
},
"app.skins.section.mounts-of-mayhem": {
"message": "Mounts of Mayhem"
},
Expand Down
42 changes: 38 additions & 4 deletions apps/app-frontend/src/pages/Skins.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,21 @@ type VirtualSkinSectionListExpose = {
}

const PENDING_SKIN_REFRESH_DELAY_MS = 11_000
const DEFAULT_SKIN_SECTION_SORT_ORDER = ['Modrinth Pride', 'Default skins']
const messages = defineMessages({
skinSelectorTitle: {
id: 'app.skins.title',
defaultMessage: 'Skin selector',
},
modrinthPrideSection: {
id: 'app.skins.section.modrinth-pride',
defaultMessage: 'Modrinth Pride',
},
modrinthPrideTooltip: {
id: 'app.skins.section.modrinth-pride.tooltip',
defaultMessage:
'You received these skins for donating to a Modrinth Pride fundraiser during Pride Month.',
},
defaultSkinsSection: {
id: 'app.skins.section.default-skins',
defaultMessage: 'Default skins',
Expand Down Expand Up @@ -185,17 +195,25 @@ const defaultSkinSections = computed(() => {
const sections = new Map<string, Skin[]>()

for (const skin of defaultSkins.value) {
const sectionTitle = getDefaultSkinSectionTitle(skin.section)
const sectionSkins = sections.get(sectionTitle)
const section = skin.section ?? 'Default skins'
const sectionSkins = sections.get(section)

if (sectionSkins) {
sectionSkins.push(skin)
} else {
sections.set(sectionTitle, [skin])
sections.set(section, [skin])
}
}

return Array.from(sections, ([title, skins]) => ({ title, skins }))
return Array.from(sections, ([section, skins]) => ({
section,
title: getDefaultSkinSectionTitle(section),
infoTooltip: getDefaultSkinSectionInfoTooltip(section),
skins,
})).sort(
(a, b) =>
getDefaultSkinSectionSortIndex(a.section) - getDefaultSkinSectionSortIndex(b.section),
)
})

const currentCape = computed(() => {
Expand Down Expand Up @@ -303,6 +321,8 @@ function isMinecraftSkinRateLimitError(error: unknown) {

function getDefaultSkinSectionTitle(section?: string) {
switch (section) {
case 'Modrinth Pride':
return formatMessage(messages.modrinthPrideSection)
case 'MINECON Earth 2017':
return formatMessage(messages.mineconEarth2017Section)
case 'Builders & Biomes':
Expand All @@ -326,6 +346,20 @@ function getDefaultSkinSectionTitle(section?: string) {
}
}

function getDefaultSkinSectionInfoTooltip(section: string) {
switch (section) {
case 'Modrinth Pride':
return formatMessage(messages.modrinthPrideTooltip)
default:
return undefined
}
}

function getDefaultSkinSectionSortIndex(section: string) {
const index = DEFAULT_SKIN_SECTION_SORT_ORDER.indexOf(section)
return index === -1 ? DEFAULT_SKIN_SECTION_SORT_ORDER.length : index
}

function changeSkin(newSkin: Skin) {
selectedSkin.value = newSkin
}
Expand Down
1 change: 1 addition & 0 deletions apps/app-frontend/src/store/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const DEFAULT_FEATURE_FLAGS = {
server_ram_as_bytes_always_on: false,
always_show_app_controls: false,
skip_unknown_pack_warning: false,
pride_fundraiser: false,
i18n_debug: false,
}

Expand Down
Loading
Loading