Skip to content
Merged
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
45 changes: 45 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: tests

on:
pull_request:
push:
branches:
- "main"
- "beta"
workflow_dispatch:

concurrency:
group: tests-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
vitest:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out the repo
uses: actions/checkout@v5

# setup-node runs twice: its yarn cache probe needs Corepack first (see i18n.yml).
- name: Set up Node
uses: actions/setup-node@v5
with:
node-version-file: .nvmrc
package-manager-cache: false

- name: Enable Corepack
run: |
corepack enable
corepack prepare --activate

- name: Restore the yarn cache
uses: actions/setup-node@v5
with:
node-version-file: .nvmrc
cache: yarn

- name: Install dependencies
run: yarn install --immutable

- name: Run tests
run: yarn test
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22
24
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:22-alpine AS deps
FROM node:24-alpine AS deps

WORKDIR /opt/5stack

Expand All @@ -8,7 +8,7 @@ RUN corepack enable && corepack prepare

RUN yarn install --immutable

FROM node:22-alpine AS builder
FROM node:24-alpine AS builder

WORKDIR /opt/5stack

Expand All @@ -25,7 +25,7 @@ ENV NODE_OPTIONS=--max-old-space-size=8192

RUN yarn build

FROM node:22-alpine
FROM node:24-alpine

WORKDIR /opt/5stack

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:22-alpine AS deps
FROM node:24-alpine AS deps

WORKDIR /opt/5stack

Expand All @@ -8,7 +8,7 @@ RUN corepack enable && corepack prepare

RUN yarn install --immutable

FROM node:22-alpine
FROM node:24-alpine

WORKDIR /opt/5stack

Expand Down
76 changes: 12 additions & 64 deletions components/InstallPWA.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { ref } from "vue";
import { MonitorDown, PlusSquare, Share } from "lucide-vue-next";
import { MonitorDown } from "lucide-vue-next";
import { useSidebar } from "@/components/ui/sidebar";
import FiveStackToolTip from "~/components/FiveStackToolTip.vue";
import { isIosBrowser, usePwaInstall } from "~/composables/usePwaInstall";
import InstallPWADrawer from "~/components/InstallPWADrawer.vue";
import { usePwaInstall } from "~/composables/usePwaInstall";
import { tacticalCtaButtonClasses } from "~/utilities/tacticalClasses";

withDefaults(
Expand All @@ -24,20 +24,8 @@ const { state, isMobile } = useSidebar();

// Shared with the notification settings gate and the enable-push prompt, so
// all three agree on what "installed" means.
const { canInstall, install } = usePwaInstall();

const installPWADrawer = ref(false);

const isIOS = isIosBrowser();

async function installPWA() {
if (isIOS) {
installPWADrawer.value = true;
return;
}

await install();
}
const { canInstall, manualInstall, showInstructions, install } =
usePwaInstall();
</script>

<template>
Expand All @@ -48,7 +36,7 @@ async function installPWA() {
:class="{ 'mx-4': isMobile || state === 'expanded' }"
>
<SidebarMenuButton as-child :tooltip="$t('pwa.install.tooltip')">
<Button @click="installPWA" size="sm">
<Button @click="install" size="sm">
<MonitorDown />
<span v-if="isMobile || state === 'expanded'">{{
$t("pwa.install.button")
Expand All @@ -60,62 +48,22 @@ async function installPWA() {
<template v-else>
<FiveStackToolTip v-if="!isMobile && !showLabel">
<template #trigger>
<Button @click="installPWA" size="sm">
<Button @click="install" size="sm">
<MonitorDown />
</Button>
</template>
{{ $t("pwa.install.button") }}
</FiveStackToolTip>
<!-- Labelled variant is a real call to action rather than a nav affordance
— it's the one thing standing between the player and a feature. -->
<Button :class="tacticalCtaButtonClasses" @click="installPWA" v-else>
<Button :class="tacticalCtaButtonClasses" @click="install" v-else>
{{ $t("pwa.install.button") }}
</Button>
</template>

<Drawer
:open="installPWADrawer"
@update:open="installPWADrawer = $event"
v-if="isIOS"
>
<DrawerContent class="p-4">
<DrawerHeader>
<div class="flex justify-between items-center">
<DrawerTitle>{{ $t("pwa.install.title") }}</DrawerTitle>
<DrawerClose>
<Button
variant="link"
@click="installPWADrawer = false"
class="text-lg text-blue-500"
>
{{ $t("common.cancel") }}
</Button>
</DrawerClose>
</div>
<Separator class="my-4" />
<DrawerDescription class="text-lg">
{{ $t("pwa.install.description") }}
</DrawerDescription>
<Separator class="my-4" />
</DrawerHeader>

<div class="flex flex-col gap-4 m-auto">
<div class="flex items-center gap-4">
<Share class="size-8 text-blue-500" />
<div class="flex gap-2 text-muted-foreground">
<span>1)</span>
<span>{{ $t("pwa.install.step1") }}</span>
</div>
</div>
<div class="flex items-center gap-4">
<PlusSquare class="size-8" />
<div class="flex gap-2 text-muted-foreground">
<span>2)</span>
<span>{{ $t("pwa.install.step2") }}</span>
</div>
</div>
</div>
</DrawerContent>
</Drawer>
<InstallPWADrawer
v-model:open="showInstructions"
:platform="manualInstall"
/>
</div>
</template>
94 changes: 94 additions & 0 deletions components/InstallPWADrawer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<script setup lang="ts">
import { computed } from "vue";
import {
EllipsisVertical,
ExternalLink,
PlusSquare,
Share,
} from "lucide-vue-next";
import type { ManualInstallPlatform } from "~/composables/usePwaInstall";

const props = defineProps<{
platform: ManualInstallPlatform | null;
}>();

const open = defineModel<boolean>("open", { default: false });

const steps = computed(() => {
switch (props.platform) {
case "ios":
return [
{ icon: Share, label: "pwa.install.step1" },
{ icon: PlusSquare, label: "pwa.install.step2" },
];
case "android":
return [
{ icon: EllipsisVertical, label: "pwa.install.android.step1" },
{ icon: PlusSquare, label: "pwa.install.android.step2" },
];
case "in_app":
return [
{ icon: ExternalLink, label: "pwa.install.in_app.step1" },
{ icon: PlusSquare, label: "pwa.install.in_app.step2" },
];
default:
return [];
}
});
</script>

<template>
<Drawer v-if="platform" :open="open" @update:open="open = $event">
<DrawerContent class="p-4">
<DrawerHeader>
<div class="flex justify-between items-center">
<DrawerTitle>{{ $t("pwa.install.title") }}</DrawerTitle>
<DrawerClose>
<Button
variant="link"
@click="open = false"
class="text-lg text-blue-500"
>
{{ $t("common.cancel") }}
</Button>
</DrawerClose>
</div>
<Separator class="my-4" />
<DrawerDescription class="text-lg">
{{
platform === "in_app"
? $t("pwa.install.in_app.description")
: $t("pwa.install.description")
}}
</DrawerDescription>
<Separator class="my-4" />
</DrawerHeader>

<div class="flex flex-col gap-4 m-auto">
<div
v-for="(step, index) in steps"
:key="step.label"
class="flex items-center gap-4"
>
<component
:is="step.icon"
class="size-8"
:class="{ 'text-blue-500': index === 0 }"
/>
<div class="flex gap-2 text-muted-foreground">
<span>{{ index + 1 }})</span>
<span>{{ $t(step.label) }}</span>
</div>
</div>
</div>

<!-- Chrome stops prompting once installed, so installed players land here too. -->
<p
v-if="platform === 'android'"
class="mx-auto mt-6 max-w-prose text-center text-xs text-muted-foreground/80"
>
{{ $t("pwa.install.android.already_installed") }}
</p>
</DrawerContent>
</Drawer>
</template>
9 changes: 5 additions & 4 deletions components/MatchTableRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
UsersIcon,
X,
} from "lucide-vue-next";
import { NuxtLink } from "#components";
import TimeAgo from "~/components/TimeAgo.vue";
import { e_match_status_enum } from "~/generated/zeus";
import mapLabel from "~/utilities/mapLabel";
Expand Down Expand Up @@ -270,7 +271,7 @@ import MatchOverviewDrawer from "~/components/match/MatchOverviewDrawer.vue";
:title="match.lineup_1.name"
>
<component
:is="match.lineup_1.team_id ? 'NuxtLink' : 'span'"
:is="match.lineup_1.team_id ? NuxtLink : 'span'"
:to="
match.lineup_1.team_id
? `/teams/${match.lineup_1.team_id}`
Expand Down Expand Up @@ -315,7 +316,7 @@ import MatchOverviewDrawer from "~/components/match/MatchOverviewDrawer.vue";
:title="match.lineup_2.name"
>
<component
:is="match.lineup_2.team_id ? 'NuxtLink' : 'span'"
:is="match.lineup_2.team_id ? NuxtLink : 'span'"
:to="
match.lineup_2.team_id
? `/teams/${match.lineup_2.team_id}`
Expand Down Expand Up @@ -366,7 +367,7 @@ import MatchOverviewDrawer from "~/components/match/MatchOverviewDrawer.vue";
}"
>
<component
:is="match.lineup_1.team_id ? 'NuxtLink' : 'span'"
:is="match.lineup_1.team_id ? NuxtLink : 'span'"
:to="
match.lineup_1.team_id
? `/teams/${match.lineup_1.team_id}`
Expand Down Expand Up @@ -430,7 +431,7 @@ import MatchOverviewDrawer from "~/components/match/MatchOverviewDrawer.vue";
}"
>
<component
:is="match.lineup_2.team_id ? 'NuxtLink' : 'span'"
:is="match.lineup_2.team_id ? NuxtLink : 'span'"
:to="
match.lineup_2.team_id
? `/teams/${match.lineup_2.team_id}`
Expand Down
Loading
Loading