fix: check if update is available based on auto-detected version channel.

This commit is contained in:
killian-larcher
2026-01-28 20:22:14 +01:00
parent 6231002043
commit 46022e8711
5 changed files with 37 additions and 37 deletions
-2
View File
@@ -29,5 +29,3 @@ AUTH_GOOGLE_METHOD=
# Retention # Retention
RETENTION_CRON="* * * * *" RETENTION_CRON="* * * * *"
# Updates
NEXT_PUBLIC_UPDATE_CHANNEL=stable
-2
View File
@@ -44,11 +44,9 @@ export const env = createEnv({
}, },
client: { client: {
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(), NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
NEXT_PUBLIC_UPDATE_CHANNEL: z.enum(["stable", "beta", "rc"]).default("stable"),
}, },
runtimeEnv: { runtimeEnv: {
NEXT_PUBLIC_PROJECT_VERSION: version || "Unknown Version", NEXT_PUBLIC_PROJECT_VERSION: version || "Unknown Version",
NEXT_PUBLIC_UPDATE_CHANNEL: process.env.NEXT_PUBLIC_UPDATE_CHANNEL || "stable",
PROJECT_NAME: process.env.PROJECT_NAME, PROJECT_NAME: process.env.PROJECT_NAME,
PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION, PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION,
@@ -1,10 +1,11 @@
"use client"; "use client";
import {useUpdateCheck} from "../hooks/use-update-check"; import {useUpdateCheck} from "../hooks/use-update-check";
import { useSidebar, SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton } from "@/components/ui/sidebar"; import {useSidebar, SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuItem} from "@/components/ui/sidebar";
import {X, ArrowUpCircle, MoveRight} from "lucide-react"; import {X, ArrowUpCircle, MoveRight} from "lucide-react";
import Link from "next/link"; import Link from "next/link";
export const UpdateNotification = () => { export const UpdateNotification = () => {
const {isUpdateAvailable, latestRelease, dismissUpdate} = useUpdateCheck(); const {isUpdateAvailable, latestRelease, dismissUpdate} = useUpdateCheck();
const {state} = useSidebar(); const {state} = useSidebar();
@@ -18,7 +19,8 @@ export const UpdateNotification = () => {
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem className="px-2"> <SidebarMenuItem className="px-2">
<div className="relative flex flex-col gap-2 rounded-lg border bg-primary/5 p-3 text-sidebar-foreground border-primary/20"> <div
className="relative flex flex-col gap-2 rounded-lg border bg-primary/5 p-3 text-sidebar-foreground border-primary/20">
<button <button
onClick={dismissUpdate} onClick={dismissUpdate}
className="absolute right-2 top-2 rounded-md p-0.5 text-muted-foreground/50 hover:bg-sidebar-accent hover:text-foreground transition-colors" className="absolute right-2 top-2 rounded-md p-0.5 text-muted-foreground/50 hover:bg-sidebar-accent hover:text-foreground transition-colors"
@@ -28,13 +30,15 @@ export const UpdateNotification = () => {
</button> </button>
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<div className="flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary"> <div
className="flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
<ArrowUpCircle className="size-4"/> <ArrowUpCircle className="size-4"/>
</div> </div>
<div className="flex flex-col min-w-0"> <div className="flex flex-col min-w-0">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-[12px] font-semibold leading-none">Update available</span> <span className="text-[12px] font-semibold leading-none">Update available</span>
<span className="text-[10px] text-muted-foreground font-medium px-1.5 py-0.5 bg-primary/10 rounded-full"> <span
className="text-[10px] text-muted-foreground font-medium px-1.5 py-0.5 bg-primary/10 rounded-full">
v{latestRelease.tag_name.replace(/^v/, "")} v{latestRelease.tag_name.replace(/^v/, "")}
</span> </span>
</div> </div>
@@ -11,13 +11,14 @@ const DISMISS_DURATION = 1000 * 60 * 60 * 24;
export const useUpdateCheck = () => { export const useUpdateCheck = () => {
const [isDismissed, setIsDismissed] = useState(true); const [isDismissed, setIsDismissed] = useState(true);
const currentVersion = env.NEXT_PUBLIC_PROJECT_VERSION!;
const {data: latestRelease, isLoading} = useQuery({ const {data: latestRelease, isLoading} = useQuery({
queryKey: ["latest-release", env.NEXT_PUBLIC_UPDATE_CHANNEL], queryKey: ["latest-release"],
queryFn: () => getLatestRelease(env.NEXT_PUBLIC_UPDATE_CHANNEL as any), queryFn: () => getLatestRelease(currentVersion),
staleTime: 1000 * 60 * 60, staleTime: 1000 * 60 * 60,
}); });
const currentVersion = env.NEXT_PUBLIC_PROJECT_VERSION;
useEffect(() => { useEffect(() => {
if (!latestRelease) return; if (!latestRelease) return;
+6 -7
View File
@@ -6,7 +6,7 @@ export interface GitHubRelease {
body: string; body: string;
} }
export const getLatestRelease = async (channel: "stable" | "beta" | "rc" = "stable"): Promise<GitHubRelease | null> => { export const getLatestRelease = async (currentVersion: string): Promise<GitHubRelease | null> => {
try { try {
const response = await fetch("https://api.github.com/repos/Portabase/portabase/releases"); const response = await fetch("https://api.github.com/repos/Portabase/portabase/releases");
if (!response.ok) { if (!response.ok) {
@@ -14,15 +14,14 @@ export const getLatestRelease = async (channel: "stable" | "beta" | "rc" = "stab
} }
const releases: GitHubRelease[] = await response.json(); const releases: GitHubRelease[] = await response.json();
if (channel === "stable") { const isStable = /^\d+\.\d+\.\d+$/.test(currentVersion);
const isRc = /^\d+\.\d+\.\d+-rc\.\d+$/.test(currentVersion);
if (isStable) {
return releases.find(r => !r.prerelease) || null; return releases.find(r => !r.prerelease) || null;
} }
if (channel === "beta") { if (isRc) {
return releases.find(r => r.tag_name.includes("beta") || !r.prerelease) || null;
}
if (channel === "rc") {
return releases.find(r => r.tag_name.includes("rc") || !r.prerelease) || null; return releases.find(r => r.tag_name.includes("rc") || !r.prerelease) || null;
} }