mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: check if update is available based on auto-detected version channel.
This commit is contained in:
@@ -29,5 +29,3 @@ AUTH_GOOGLE_METHOD=
|
|||||||
# Retention
|
# Retention
|
||||||
RETENTION_CRON="* * * * *"
|
RETENTION_CRON="* * * * *"
|
||||||
|
|
||||||
# Updates
|
|
||||||
NEXT_PUBLIC_UPDATE_CHANNEL=stable
|
|
||||||
|
|||||||
@@ -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,13 +1,14 @@
|
|||||||
"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();
|
||||||
|
|
||||||
if (!isUpdateAvailable || !latestRelease || state !== "expanded") {
|
if (!isUpdateAvailable || !latestRelease || state !== "expanded") {
|
||||||
return null;
|
return null;
|
||||||
@@ -18,33 +19,36 @@ 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
|
||||||
<button
|
className="relative flex flex-col gap-2 rounded-lg border bg-primary/5 p-3 text-sidebar-foreground border-primary/20">
|
||||||
|
<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"
|
||||||
>
|
>
|
||||||
<X className="size-3" />
|
<X className="size-3"/>
|
||||||
<span className="sr-only">Dismiss</span>
|
<span className="sr-only">Dismiss</span>
|
||||||
</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
|
||||||
<ArrowUpCircle className="size-4" />
|
className="flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||||
|
<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>
|
||||||
<Link
|
<Link
|
||||||
href={latestRelease.html_url}
|
href={latestRelease.html_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="group inline-flex items-center gap-1 text-[11px] text-primary hover:underline font-medium mt-1"
|
className="group inline-flex items-center gap-1 text-[11px] text-primary hover:underline font-medium mt-1"
|
||||||
>
|
>
|
||||||
See what's new
|
See what's new
|
||||||
<MoveRight className="size-3 transition-transform group-hover:translate-x-0.5" />
|
<MoveRight className="size-3 transition-transform group-hover:translate-x-0.5"/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import { getLatestRelease } from "../services/github";
|
import {getLatestRelease} from "../services/github";
|
||||||
import { env } from "@/env.mjs";
|
import {env} from "@/env.mjs";
|
||||||
import { useEffect, useState } from "react";
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
const DISMISS_KEY = "portabase-update-dismissed";
|
const DISMISS_KEY = "portabase-update-dismissed";
|
||||||
const DISMISS_DURATION = 1000 * 60 * 60 * 24;
|
const DISMISS_DURATION = 1000 * 60 * 60 * 24;
|
||||||
|
|
||||||
export const useUpdateCheck = () => {
|
export const useUpdateCheck = () => {
|
||||||
const [isDismissed, setIsDismissed] = useState(true);
|
const [isDismissed, setIsDismissed] = useState(true);
|
||||||
|
|
||||||
const { data: latestRelease, isLoading } = useQuery({
|
const currentVersion = env.NEXT_PUBLIC_PROJECT_VERSION!;
|
||||||
queryKey: ["latest-release", env.NEXT_PUBLIC_UPDATE_CHANNEL],
|
|
||||||
queryFn: () => getLatestRelease(env.NEXT_PUBLIC_UPDATE_CHANNEL as any),
|
const {data: latestRelease, isLoading} = useQuery({
|
||||||
|
queryKey: ["latest-release"],
|
||||||
|
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;
|
||||||
@@ -28,7 +29,7 @@ export const useUpdateCheck = () => {
|
|||||||
if (latestVersion && cleanCurrentVersion && latestVersion !== cleanCurrentVersion) {
|
if (latestVersion && cleanCurrentVersion && latestVersion !== cleanCurrentVersion) {
|
||||||
const dismissedData = localStorage.getItem(DISMISS_KEY);
|
const dismissedData = localStorage.getItem(DISMISS_KEY);
|
||||||
if (dismissedData) {
|
if (dismissedData) {
|
||||||
const { version, timestamp } = JSON.parse(dismissedData);
|
const {version, timestamp} = JSON.parse(dismissedData);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (version === latestRelease.tag_name && now - timestamp < DISMISS_DURATION) {
|
if (version === latestRelease.tag_name && now - timestamp < DISMISS_DURATION) {
|
||||||
setIsDismissed(true);
|
setIsDismissed(true);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user