mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
Merge pull request #56 from yusufipk/fix/upload-upgrade-link
fix(upload): give a full trial account the upgrade link where it reads the refusal
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { CheckCircle2, Loader2, UploadCloud, XCircle } from 'lucide-react';
|
import { CheckCircle2, Loader2, UploadCloud, XCircle } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { resolvePublicBunnyCdnHostname } from '@/lib/bunny-cdn';
|
import { resolvePublicBunnyCdnHostname } from '@/lib/bunny-cdn';
|
||||||
|
import { isTrialStorageError, toastApiError } from '@/lib/client/api-error';
|
||||||
import {
|
import {
|
||||||
cleanupPendingProjectUpload,
|
cleanupPendingProjectUpload,
|
||||||
getDefaultTitleFromFile,
|
getDefaultTitleFromFile,
|
||||||
@@ -46,6 +48,8 @@ type QueueItem = {
|
|||||||
status: QueueItemStatus;
|
status: QueueItemStatus;
|
||||||
progress: number;
|
progress: number;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
/** The failure was the trial ceiling, so the row shows the way out. */
|
||||||
|
errorIsTrialLimit?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface VideoDragDropUploaderProps {
|
interface VideoDragDropUploaderProps {
|
||||||
@@ -306,12 +310,17 @@ export function VideoDragDropUploader({
|
|||||||
failCount += 1;
|
failCount += 1;
|
||||||
|
|
||||||
const message = error instanceof Error ? error.message : 'Failed to upload video';
|
const message = error instanceof Error ? error.message : 'Failed to upload video';
|
||||||
|
const isTrialLimit = isTrialStorageError(error);
|
||||||
setQueue((prev) =>
|
setQueue((prev) =>
|
||||||
prev.map((entry) =>
|
prev.map((entry) =>
|
||||||
entry.id === item.id ? { ...entry, status: 'error', error: message } : entry
|
entry.id === item.id
|
||||||
|
? { ...entry, status: 'error', error: message, errorIsTrialLimit: isTrialLimit }
|
||||||
|
: entry
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
toast.error(`${item.file.name}: ${message}`);
|
// Keeps the error code alive to the toast: a trial account that has run
|
||||||
|
// out of room is shown the plan rather than just told the upload failed.
|
||||||
|
toastApiError(error, 'Failed to upload video', { prefix: item.file.name });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,7 +509,17 @@ export function VideoDragDropUploader({
|
|||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="truncate font-medium">{item.file.name}</p>
|
<p className="truncate font-medium">{item.file.name}</p>
|
||||||
{item.error ? (
|
{item.error ? (
|
||||||
<p className="truncate text-xs text-destructive">{item.error}</p>
|
<p className="text-xs text-destructive">
|
||||||
|
<span className="align-middle">{item.error}</span>
|
||||||
|
{item.errorIsTrialLimit && (
|
||||||
|
<Link
|
||||||
|
href="/settings"
|
||||||
|
className="ml-1 align-middle font-medium underline underline-offset-2"
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="truncate text-xs text-muted-foreground">
|
<p className="truncate text-xs text-muted-foreground">
|
||||||
{getDefaultTitleFromFile(item.file)}
|
{getDefaultTitleFromFile(item.file)}
|
||||||
|
|||||||
+12
-2
@@ -42,6 +42,14 @@ export function apiRequestError(
|
|||||||
return new ApiRequestError(payload?.error || fallback, payload?.code);
|
return new ApiRequestError(payload?.error || fallback, payload?.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this failure is the trial ceiling, for callers that draw the way out
|
||||||
|
* themselves rather than handing it to `toastApiError`.
|
||||||
|
*/
|
||||||
|
export function isTrialStorageError(source: unknown): boolean {
|
||||||
|
return codeOf(source) === API_ERROR_CODES.TRIAL_STORAGE_LIMIT_EXCEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
function codeOf(source: unknown): string | null {
|
function codeOf(source: unknown): string | null {
|
||||||
if (source instanceof ApiRequestError) return source.code;
|
if (source instanceof ApiRequestError) return source.code;
|
||||||
if (source && typeof source === 'object' && 'code' in source) {
|
if (source && typeof source === 'object' && 'code' in source) {
|
||||||
@@ -82,13 +90,15 @@ export function toastApiError(
|
|||||||
const message = messageOf(source, fallback);
|
const message = messageOf(source, fallback);
|
||||||
const text = options.prefix ? `${options.prefix}: ${message}` : message;
|
const text = options.prefix ? `${options.prefix}: ${message}` : message;
|
||||||
|
|
||||||
if (codeOf(source) === API_ERROR_CODES.TRIAL_STORAGE_LIMIT_EXCEEDED) {
|
if (isTrialStorageError(source)) {
|
||||||
toast.error(text, {
|
toast.error(text, {
|
||||||
// Longer than a plain error: this one is asking for a decision rather than
|
// Longer than a plain error: this one is asking for a decision rather than
|
||||||
// just reporting, and it disappears under the cursor at the usual timing.
|
// just reporting, and it disappears under the cursor at the usual timing.
|
||||||
duration: 12000,
|
duration: 12000,
|
||||||
action: {
|
action: {
|
||||||
label: 'See plans',
|
// The message ends in "Upgrade to get 200 GB", so the button is the verb
|
||||||
|
// that sentence just used rather than a second name for the same thing.
|
||||||
|
label: 'Upgrade',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.location.href = '/settings';
|
window.location.href = '/settings';
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user