mirror of
https://github.com/hansputera/tiktok-dl.git
synced 2026-04-05 19:51:57 +02:00
feat: added api
Signed-off-by: Hanif Dwy Putra S <hanifdwyputrasembiring@gmail.com>
This commit is contained in:
@@ -15,9 +15,9 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"ioredis": "^5.7.0",
|
||||
"ky": "^1.9.1",
|
||||
"lucide-react": "^0.542.0",
|
||||
"next": "15.5.2",
|
||||
"ow": "^2.0.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-hook-form": "^7.62.0",
|
||||
|
||||
9
apps/web/src/api/api.ts
Normal file
9
apps/web/src/api/api.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import ky from "ky";
|
||||
|
||||
export const apiClient = ky.extend({
|
||||
referrerPolicy: 'same-origin',
|
||||
credentials: 'same-origin',
|
||||
priority: 'high',
|
||||
throwHttpErrors: false,
|
||||
cache: 'force-cache',
|
||||
});
|
||||
8
apps/web/src/api/types.ts
Normal file
8
apps/web/src/api/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ExtractedInfo } from "tiktok-dl-core"
|
||||
|
||||
export type DownloadResponse = {
|
||||
data: ExtractedInfo & {
|
||||
provider: string;
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { downloadValidator } from "@/app/validators/download.validator";
|
||||
import { downloadValidator } from "@/validators/download.validator";
|
||||
import { rotateProvider } from "@/services/rotator";
|
||||
import { NextRequest } from "next/server";
|
||||
import { getProvider } from "tiktok-dl-core";
|
||||
|
||||
@@ -1,14 +1,191 @@
|
||||
'use client';
|
||||
|
||||
import { apiClient } from "@/api/api";
|
||||
import { DownloadResponse } from "@/api/types";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { getTikTokURL } from "@/lib/utils";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { Download, ExternalLink, Loader2 } from "lucide-react";
|
||||
import z from "zod";
|
||||
|
||||
const VideoPlayer = ({
|
||||
videoData,
|
||||
currentTime,
|
||||
isPlaying,
|
||||
onTimeUpdate,
|
||||
onPlayStateChange,
|
||||
isActive = true
|
||||
}: {
|
||||
videoData: DownloadResponse['data'];
|
||||
currentTime: number;
|
||||
isPlaying: boolean;
|
||||
onTimeUpdate: (time: number) => void;
|
||||
onPlayStateChange: (playing: boolean) => void;
|
||||
isActive?: boolean;
|
||||
}) => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (video && isActive) {
|
||||
video.currentTime = currentTime;
|
||||
if (isPlaying) {
|
||||
video.play().catch(() => {
|
||||
onPlayStateChange(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [videoData.video?.urls[0], isActive]);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (video && isActive && Math.abs(video.currentTime - currentTime) > 1) {
|
||||
video.currentTime = currentTime;
|
||||
}
|
||||
}, [currentTime, isActive]);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (video) {
|
||||
if (!isActive) {
|
||||
if (!video.paused) {
|
||||
video.pause();
|
||||
}
|
||||
} else {
|
||||
if (isPlaying && video.paused) {
|
||||
video.currentTime = currentTime;
|
||||
video.play().catch(() => onPlayStateChange(false));
|
||||
} else if (!isPlaying && !video.paused) {
|
||||
video.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isPlaying, isActive, currentTime]);
|
||||
|
||||
const handleTimeUpdate = () => {
|
||||
if (videoRef.current && isActive) {
|
||||
onTimeUpdate(videoRef.current.currentTime);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePlay = () => {
|
||||
if (isActive) {
|
||||
onPlayStateChange(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePause = () => {
|
||||
if (isActive) {
|
||||
onPlayStateChange(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-6 shadow-lg border">
|
||||
<h3 className="text-xl lg:text-2xl font-semibold mb-4">Video Preview</h3>
|
||||
<div className="relative">
|
||||
<video
|
||||
ref={videoRef}
|
||||
controls
|
||||
className="w-full aspect-video rounded-lg shadow-md bg-black"
|
||||
src={videoData.video?.urls[0]}
|
||||
poster={videoData.video?.thumb}
|
||||
preload="metadata"
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onPlay={handlePlay}
|
||||
onPause={handlePause}
|
||||
onLoadedMetadata={handleTimeUpdate}
|
||||
>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
<div className="mt-4 text-sm text-gray-600">
|
||||
<p><strong>Provider:</strong> {videoData.provider}</p>
|
||||
<p><strong>Available formats:</strong> {videoData.video?.urls.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DownloadOptions = ({ videoData, onDownload }: {
|
||||
videoData: DownloadResponse['data'],
|
||||
onDownload: (url: string, index: number) => void
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-6 shadow-lg border">
|
||||
<h3 className="text-xl lg:text-2xl font-semibold mb-4">
|
||||
Download Options
|
||||
<span className="text-base font-normal text-gray-500 ml-2">
|
||||
({videoData.video?.urls.length} available)
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{videoData.video?.urls.map((url, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="neutral"
|
||||
onClick={() => onDownload(url, index)}
|
||||
className="w-full justify-start p-4 h-auto hover:bg-gray-50 transition-colors"
|
||||
size="lg"
|
||||
>
|
||||
<Download className="w-5 h-5 mr-3 flex-shrink-0" />
|
||||
<div className="text-left flex-1">
|
||||
<div className="font-medium text-base">
|
||||
Quality {index + 1} {index === 0 ? "(HD)" : index === 1 ? "(Standard)" : "(Alternative)"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mt-1">
|
||||
Click to download MP4 file
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
|
||||
<div className="pt-3 border-t mt-4">
|
||||
<Button
|
||||
variant="noShadow"
|
||||
onClick={() => window.open(videoData.video?.urls[0], '_blank')}
|
||||
className="w-full justify-start p-4 h-auto hover:bg-gray-50 transition-colors"
|
||||
size="lg"
|
||||
>
|
||||
<ExternalLink className="w-5 h-5 mr-3 flex-shrink-0" />
|
||||
<div className="text-left">
|
||||
<div className="font-medium">View in new tab</div>
|
||||
<div className="text-sm text-gray-500 mt-1">
|
||||
Open video directly in browser
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
const [videoData, setVideoData] = useState<DownloadResponse | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [videoCurrentTime, setVideoCurrentTime] = useState(0);
|
||||
const [videoIsPlaying, setVideoIsPlaying] = useState(false);
|
||||
|
||||
const [activePlayer, setActivePlayer] = useState<'mobile' | 'desktop'>('desktop');
|
||||
|
||||
useEffect(() => {
|
||||
const checkScreenSize = () => {
|
||||
setActivePlayer(window.innerWidth >= 1024 ? 'desktop' : 'mobile');
|
||||
};
|
||||
|
||||
checkScreenSize();
|
||||
|
||||
window.addEventListener('resize', checkScreenSize);
|
||||
return () => window.removeEventListener('resize', checkScreenSize);
|
||||
}, []);
|
||||
|
||||
const formSchema = z.object({
|
||||
url: z.url().refine((val) => getTikTokURL(val), {
|
||||
error: 'Invalid VT Tiktok URL',
|
||||
@@ -22,26 +199,82 @@ export default function Home() {
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (values: z.infer<typeof formSchema>) => {
|
||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('./api/download', {
|
||||
json: {
|
||||
url: values.url,
|
||||
},
|
||||
}).json<DownloadResponse>();
|
||||
|
||||
}
|
||||
if (response.message) {
|
||||
form.setError('url', {
|
||||
message: response.message,
|
||||
});
|
||||
setVideoData(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setVideoData(response);
|
||||
setVideoCurrentTime(0);
|
||||
setVideoIsPlaying(false);
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
form.setError('url', {
|
||||
message: 'Failed to process video. Please try again.',
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = (url: string, index: number) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `tiktok_video_${index + 1}.mp4`;
|
||||
link.target = '_blank';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setVideoData(null);
|
||||
setVideoCurrentTime(0);
|
||||
setVideoIsPlaying(false);
|
||||
form.reset();
|
||||
};
|
||||
|
||||
const handleTimeUpdate = (time: number) => {
|
||||
setVideoCurrentTime(time);
|
||||
};
|
||||
|
||||
const handlePlayStateChange = (playing: boolean) => {
|
||||
setVideoIsPlaying(playing);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center lg:justify-center">
|
||||
<div className="w-full max-w-6xl mx-auto px-4">
|
||||
{/* Mobile Layout */}
|
||||
<div className="lg:hidden">
|
||||
<h1 className="text-4xl font-sans text-black mb-6">
|
||||
<div className="min-h-screen">
|
||||
{/* Mobile/Tablet Layout - Stack Vertically */}
|
||||
<div className="lg:hidden p-4">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-sans text-black mb-6 text-center">
|
||||
Download TikTok Videos!
|
||||
</h1>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 mb-6">
|
||||
<FormField control={form.control} name="url" render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xl">VT URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Tiktok video URL (e.g. https://vt.tiktok.com/XXXXXX)" {...field} />
|
||||
<Input
|
||||
placeholder="Tiktok video URL (e.g. https://vt.tiktok.com/XXXXXX)"
|
||||
className="text-lg py-3"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Please provide the tiktok video URL to download
|
||||
@@ -50,50 +283,100 @@ export default function Home() {
|
||||
</FormItem>
|
||||
)} />
|
||||
|
||||
<Button type="submit">
|
||||
Download
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" disabled={isLoading} className="flex-1">
|
||||
{isLoading && <Loader2 className="w-4 h-4 mr-2 animate-spin" />}
|
||||
{isLoading ? "Processing..." : "Download"}
|
||||
</Button>
|
||||
{videoData && (
|
||||
<Button type="button" variant="neutral" onClick={resetForm}>
|
||||
New Video
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
{/* Video Results - Stack Vertically on Mobile */}
|
||||
{videoData?.data && (
|
||||
<div className="space-y-6">
|
||||
<VideoPlayer
|
||||
videoData={videoData.data}
|
||||
currentTime={videoCurrentTime}
|
||||
isPlaying={videoIsPlaying}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onPlayStateChange={handlePlayStateChange}
|
||||
isActive={activePlayer === 'mobile'}
|
||||
/>
|
||||
<DownloadOptions videoData={videoData.data} onDownload={handleDownload} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Large Screen Hero Layout */}
|
||||
<div className="hidden lg:flex lg:items-center lg:gap-12">
|
||||
{/* Hero Title */}
|
||||
<div className="flex-shrink-0">
|
||||
<h1 className="text-6xl font-sans text-black leading-tight">
|
||||
Download<br />
|
||||
TikTok<br />
|
||||
Videos!
|
||||
</h1>
|
||||
</div>
|
||||
{/* Large Screen Hero Layout - Side by Side */}
|
||||
<div className="hidden lg:flex lg:min-h-screen lg:items-center lg:justify-center">
|
||||
<div className="w-full max-w-7xl mx-auto px-6">
|
||||
<div className="flex items-start gap-12">
|
||||
{/* Hero Title */}
|
||||
<div className="flex-shrink-0">
|
||||
<h1 className="text-6xl font-sans text-black leading-tight">
|
||||
Download<br />
|
||||
TikTok<br />
|
||||
Videos!
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Form Section */}
|
||||
<div className="flex-1 max-w-md">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField control={form.control} name="url" render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xl">VT URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Tiktok video URL (e.g. https://vt.tiktok.com/XXXXXX)"
|
||||
className="text-lg py-3"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription className="text-base">
|
||||
Please provide the tiktok video URL to download
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
{/* Form and Video Section */}
|
||||
<div className="flex-1 max-w-5xl">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 mb-8">
|
||||
<FormField control={form.control} name="url" render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xl">VT URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Tiktok video URL (e.g. https://vt.tiktok.com/XXXXXX)"
|
||||
className="text-lg py-3"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription className="text-base">
|
||||
Please provide the tiktok video URL to download
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
|
||||
<Button type="submit" size="lg" className="w-full">
|
||||
Download
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
<div className="flex gap-3">
|
||||
<Button type="submit" size="lg" disabled={isLoading} className="flex-1 max-w-xs">
|
||||
{isLoading && <Loader2 className="w-5 h-5 mr-2 animate-spin" />}
|
||||
{isLoading ? "Processing..." : "Download"}
|
||||
</Button>
|
||||
{videoData && (
|
||||
<Button type="button" variant="neutral" size="lg" onClick={resetForm}>
|
||||
New Video
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
{/* Video Results - Side by Side on Desktop */}
|
||||
{videoData?.data && (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||
<VideoPlayer
|
||||
videoData={videoData.data}
|
||||
currentTime={videoCurrentTime}
|
||||
isPlaying={videoIsPlaying}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onPlayStateChange={handlePlayStateChange}
|
||||
isActive={activePlayer === 'desktop'}
|
||||
/>
|
||||
<DownloadOptions videoData={videoData.data} onDownload={handleDownload} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,8 @@ export const downloadValidator = z.object({
|
||||
url: z.url().refine((url) => getTikTokURL(url), {
|
||||
error: 'Invalid VT URL',
|
||||
}),
|
||||
type: z.enum(Providers.map(provider => provider.resourceName()).concat('random')),
|
||||
type: z.enum(Providers.map(provider => provider.resourceName()).concat('random')).default('random'),
|
||||
rotateOnError: z.boolean().default(true),
|
||||
nocache: z.boolean().default(false),
|
||||
params: z.object().optional(),
|
||||
params: z.object().optional().default({}),
|
||||
});
|
||||
@@ -30,8 +30,15 @@ export const Providers: BaseProvider[] = [
|
||||
// new GetVidTikProvider(),
|
||||
];
|
||||
|
||||
export const getRandomProvider = () =>
|
||||
Providers[Math.floor(Math.random() * Providers.length)];
|
||||
export const getRandomProvider = (): BaseProvider => {
|
||||
const provider = Providers[Math.floor(Math.random() * Providers.length)]
|
||||
while(provider.resourceName() === 'native')
|
||||
{
|
||||
return getRandomProvider();
|
||||
}
|
||||
|
||||
return provider;
|
||||
};
|
||||
|
||||
export const getProvider = (name: string) =>
|
||||
name.toLowerCase() !== 'random'
|
||||
|
||||
126
yarn.lock
126
yarn.lock
@@ -682,13 +682,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sindresorhus/is@npm:^6.3.0":
|
||||
version: 6.3.1
|
||||
resolution: "@sindresorhus/is@npm:6.3.1"
|
||||
checksum: 10/d28893760d7cb347a28164d1ceb55150b6bb66c5771d0c4dbefd88db63b9e4dac945d9afde2af2e0e113b1ab498985dc58d0c7e6c2ae9cc94aebc5b24d74d92c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standard-schema/utils@npm:^0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "@standard-schema/utils@npm:0.3.0"
|
||||
@@ -1681,13 +1674,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"callsites@npm:^4.1.0":
|
||||
version: 4.2.0
|
||||
resolution: "callsites@npm:4.2.0"
|
||||
checksum: 10/9a740675712076a38208967d7f80b525c9c7f4524c2af5d3936c5e278a601af0423a07e91f79679fec0546f3a52514d56969c6fe65f84d794e64a36b1f5eda8a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"caniuse-lite@npm:^1.0.30001579":
|
||||
version: 1.0.30001737
|
||||
resolution: "caniuse-lite@npm:1.0.30001737"
|
||||
@@ -1785,13 +1771,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"convert-hrtime@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "convert-hrtime@npm:5.0.0"
|
||||
checksum: 10/5245ad1ac6dd57b2d87624ae0eeac1d2a74812a6631208c09368bef787a28e7dbfa736cddaa9c8a0c425cb240437ea506afec7b9684ff617004d06a551f26c87
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.2":
|
||||
version: 7.0.3
|
||||
resolution: "cross-spawn@npm:7.0.3"
|
||||
@@ -1980,15 +1959,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dot-prop@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "dot-prop@npm:8.0.2"
|
||||
dependencies:
|
||||
type-fest: "npm:^3.8.0"
|
||||
checksum: 10/b321e43393c6efba35875c493ebfc6115d8a56c251431e88f055b82224e104c8a6eeb567877339715fb81cdbb67009bfa9cffb57cc423a560756874989dabb45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "dunder-proto@npm:1.0.1"
|
||||
@@ -2017,13 +1987,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"environment@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "environment@npm:1.1.0"
|
||||
checksum: 10/dd3c1b9825e7f71f1e72b03c2344799ac73f2e9ef81b78ea8b373e55db021786c6b9f3858ea43a436a2c4611052670ec0afe85bc029c384cc71165feee2f4ba6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0":
|
||||
version: 1.24.0
|
||||
resolution: "es-abstract@npm:1.24.0"
|
||||
@@ -2610,13 +2573,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-equals@npm:^5.0.1":
|
||||
version: 5.2.2
|
||||
resolution: "fast-equals@npm:5.2.2"
|
||||
checksum: 10/87939dc01c6634f844369c2d774c9bf82b6c5935eb45c698fdfd2e708439c6c94a67a41c67c7e063759394e319850ee563e717e65776c8f5997566b0cbb17c7a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-glob@npm:3.3.1":
|
||||
version: 3.3.1
|
||||
resolution: "fast-glob@npm:3.3.1"
|
||||
@@ -2801,13 +2757,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"function-timeout@npm:^1.0.1":
|
||||
version: 1.0.2
|
||||
resolution: "function-timeout@npm:1.0.2"
|
||||
checksum: 10/3afedebacaaf237ba9aaef925886fcf5abd434ca12a18c1c7cecb001e57bf9b30434278edcc977a127baeb5b6361f7c278243c1dbf8bf349aa8b30500c57a699
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "function.prototype.name@npm:1.1.8"
|
||||
@@ -3093,15 +3042,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"identifier-regex@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "identifier-regex@npm:1.0.0"
|
||||
dependencies:
|
||||
reserved-identifiers: "npm:^1.0.0"
|
||||
checksum: 10/4c18d94de9c3bd48c6f8e810084a8003d216ef4be88a7f37714ada2bb3cdd6a21e0fd918eb9d6b8417b3bbc36876cc2984627a99715a97375d1a31fc2b9f04bb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ignore@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "ignore@npm:5.2.0"
|
||||
@@ -3311,16 +3251,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-identifier@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "is-identifier@npm:1.0.1"
|
||||
dependencies:
|
||||
identifier-regex: "npm:^1.0.0"
|
||||
super-regex: "npm:^1.0.0"
|
||||
checksum: 10/c882f78ce47c04bbbc2cd6ce410a854c236162a03633b3dc9450347d1c60094f3a6b2a433913b6a40692d53901b3269eed42fbc5971c1c5ebed14cde718ba26c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-map@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "is-map@npm:2.0.3"
|
||||
@@ -3544,6 +3474,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ky@npm:^1.9.1":
|
||||
version: 1.9.1
|
||||
resolution: "ky@npm:1.9.1"
|
||||
checksum: 10/61b4f3d4614d26583be2d48b8977bcaaa8332cb24fc02a0205c132fa92eefe9c0a424326aa6820c404465dd47303e299798339a0237e527d479f84aa5db13e6b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"language-subtag-registry@npm:^0.3.20":
|
||||
version: 0.3.23
|
||||
resolution: "language-subtag-registry@npm:0.3.23"
|
||||
@@ -4077,20 +4014,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ow@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "ow@npm:2.0.0"
|
||||
dependencies:
|
||||
"@sindresorhus/is": "npm:^6.3.0"
|
||||
callsites: "npm:^4.1.0"
|
||||
dot-prop: "npm:^8.0.2"
|
||||
environment: "npm:^1.0.0"
|
||||
fast-equals: "npm:^5.0.1"
|
||||
is-identifier: "npm:^1.0.0"
|
||||
checksum: 10/549c2db4efdb93c1adf4b73a35b2930c4361a184ca0728db19111fa61efbbde813165e3c82690b7268075daa42325a98d9ae56c7f37a96974e9b7c9bffbe6ac1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"own-keys@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "own-keys@npm:1.0.1"
|
||||
@@ -4363,13 +4286,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"reserved-identifiers@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "reserved-identifiers@npm:1.0.0"
|
||||
checksum: 10/95b4cdedd57a2589e76012c66f9f4414ae5d25178a79a8b3d11a16b080616af7f790f8f55dc7825813d1a83a383de19492f021998484f834dca0f5a3b015a536
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve-alpn@npm:^1.2.0":
|
||||
version: 1.2.1
|
||||
resolution: "resolve-alpn@npm:1.2.1"
|
||||
@@ -4901,16 +4817,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"super-regex@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "super-regex@npm:1.0.0"
|
||||
dependencies:
|
||||
function-timeout: "npm:^1.0.1"
|
||||
time-span: "npm:^5.1.0"
|
||||
checksum: 10/d99e90ee0950356b86b01ad327605080e72ee0712c7e5c66335e7e4e3bd2919206caea929fa2d5ca97c2afc1d1ab91466d09eadcf1101196edcfb94bebfea388
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supports-color@npm:^7.1.0":
|
||||
version: 7.2.0
|
||||
resolution: "supports-color@npm:7.2.0"
|
||||
@@ -4995,15 +4901,6 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"time-span@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "time-span@npm:5.1.0"
|
||||
dependencies:
|
||||
convert-hrtime: "npm:^5.0.0"
|
||||
checksum: 10/949c45fcb873f2d26fda3db1b7f7161ce65206f6e94a7c6c9bf3a5a07a373570dba57ca5c1f816efa6326adbc3f9e93bb6ef19a7a220f4259a917e1192d49418
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinyglobby@npm:^0.2.13":
|
||||
version: 0.2.14
|
||||
resolution: "tinyglobby@npm:0.2.14"
|
||||
@@ -5179,13 +5076,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^3.8.0":
|
||||
version: 3.13.1
|
||||
resolution: "type-fest@npm:3.13.1"
|
||||
checksum: 10/9a8a2359ada34c9b3affcaf3a8f73ee14c52779e89950db337ce66fb74c3399776c697c99f2532e9b16e10e61cfdba3b1c19daffb93b338b742f0acd0117ce12
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typed-array-buffer@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "typed-array-buffer@npm:1.0.3"
|
||||
@@ -5390,9 +5280,9 @@ __metadata:
|
||||
eslint: "npm:^9"
|
||||
eslint-config-next: "npm:15.5.2"
|
||||
ioredis: "npm:^5.7.0"
|
||||
ky: "npm:^1.9.1"
|
||||
lucide-react: "npm:^0.542.0"
|
||||
next: "npm:15.5.2"
|
||||
ow: "npm:^2.0.0"
|
||||
react: "npm:19.1.0"
|
||||
react-dom: "npm:19.1.0"
|
||||
react-hook-form: "npm:^7.62.0"
|
||||
|
||||
Reference in New Issue
Block a user