v7.0.6
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { openExternal } from "@/lib/utils";
|
||||
import { GetOSInfo } from "../../wailsjs/go/main/App";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { Bug, Lightbulb, ExternalLink } from "lucide-react";
|
||||
import ExyezedIcon from "@/assets/icons/exyezed.svg";
|
||||
import SpotubeDLIcon from "@/assets/icons/spotubedl.svg";
|
||||
import SpotiDownloaderIcon from "@/assets/icons/spotidownloader.svg";
|
||||
import XBatchDLIcon from "@/assets/icons/xbatchdl.svg";
|
||||
interface AboutPageProps {
|
||||
version: string;
|
||||
}
|
||||
export function AboutPage({ version }: AboutPageProps) {
|
||||
const [os, setOs] = useState("Unknown");
|
||||
const [location, setLocation] = useState("Unknown");
|
||||
const [reportType, setReportType] = useState("bug");
|
||||
const [problem, setProblem] = useState("");
|
||||
const [bugType, setBugType] = useState<string>("Track");
|
||||
const [spotifyUrl, setSpotifyUrl] = useState("");
|
||||
const [bugContext, setBugContext] = useState("");
|
||||
const [featureDesc, setFeatureDesc] = useState("");
|
||||
const [useCase, setUseCase] = useState("");
|
||||
const [featureContext, setFeatureContext] = useState("");
|
||||
useEffect(() => {
|
||||
const fetchOS = async () => {
|
||||
try {
|
||||
const info = await GetOSInfo();
|
||||
setOs(info);
|
||||
}
|
||||
catch (err) {
|
||||
const userAgent = window.navigator.userAgent;
|
||||
if (userAgent.indexOf("Win") !== -1)
|
||||
setOs("Windows");
|
||||
else if (userAgent.indexOf("Mac") !== -1)
|
||||
setOs("macOS");
|
||||
else if (userAgent.indexOf("Linux") !== -1)
|
||||
setOs("Linux");
|
||||
}
|
||||
};
|
||||
fetchOS();
|
||||
const fetchLocation = async () => {
|
||||
try {
|
||||
const response = await fetch('https://ipapi.co/json/');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const city = data.city || '';
|
||||
const region = data.region || '';
|
||||
const country = data.country_name || '';
|
||||
const parts = [city, region, country].filter(Boolean);
|
||||
setLocation(parts.join(', ') || 'Unknown');
|
||||
}
|
||||
else {
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
setLocation(timezone);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
setLocation(timezone);
|
||||
}
|
||||
};
|
||||
fetchLocation();
|
||||
}, []);
|
||||
const faqs = [
|
||||
{
|
||||
q: "Is this software free?",
|
||||
a: "Yes. This software is completely free. You do not need an account, login, or subscription. All you need is an internet connection."
|
||||
},
|
||||
{
|
||||
q: "Can using this software get my Spotify account suspended or banned?",
|
||||
a: "No. This software has no connection to your Spotify account. Spotify data is obtained through reverse engineering of the Spotify Web Player, not through user authentication."
|
||||
},
|
||||
{
|
||||
q: "Where does the audio come from?",
|
||||
a: "The audio is fetched using third-party APIs."
|
||||
},
|
||||
{
|
||||
q: "Why does metadata fetching sometimes fail?",
|
||||
a: "This usually happens because your IP address has been rate-limited. You can wait and try again later, or use a VPN to bypass the rate limit."
|
||||
},
|
||||
{
|
||||
q: "Why does Windows Defender or antivirus flag or delete the file?",
|
||||
a: "This is a false positive. It likely happens because the executable is compressed using UPX. If you are concerned, you can fork the repository and build the software yourself from source."
|
||||
}
|
||||
];
|
||||
const sanitizeForURL = (text: string): string => {
|
||||
return text.replace(/[()]/g, "").replace(/,/g, " -");
|
||||
};
|
||||
const handleSubmit = () => {
|
||||
let title = "";
|
||||
let body = "";
|
||||
if (reportType === "bug") {
|
||||
title = `[Bug Report] ${problem ? problem.substring(0, 50) + (problem.length > 50 ? "..." : "") : "Issue"}`;
|
||||
body = `### [Bug Report]
|
||||
|
||||
#### Problem
|
||||
> ${problem || "Type here"}
|
||||
|
||||
#### Type
|
||||
${bugType || "Track / Album / Playlist / Artist"}
|
||||
|
||||
#### Spotify URL
|
||||
> ${spotifyUrl || "Type here"}
|
||||
|
||||
#### Additional Context
|
||||
> ${bugContext || "Type here or send screenshot/recording"}
|
||||
|
||||
#### Version
|
||||
SpotiFLAC v${version}
|
||||
|
||||
#### OS
|
||||
${sanitizeForURL(os || "Unknown")}
|
||||
|
||||
#### Location
|
||||
${location || "Unknown"}
|
||||
`;
|
||||
}
|
||||
else {
|
||||
title = `[Feature Request] ${featureDesc ? featureDesc.substring(0, 50) + (featureDesc.length > 50 ? "..." : "") : "Request"}`;
|
||||
body = `### [Feature Request]
|
||||
|
||||
#### Description
|
||||
> ${featureDesc || "Type here"}
|
||||
|
||||
#### Use Case
|
||||
> ${useCase || "Type here"}
|
||||
|
||||
#### Additional Context
|
||||
> ${featureContext || "Type here or send screenshot/recording"}
|
||||
`;
|
||||
}
|
||||
const url = `https://github.com/afkarxyz/SpotiFLAC/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
|
||||
openExternal(url);
|
||||
};
|
||||
return (<div className="animate-in slide-in-from-bottom-12 fade-in duration-500 ease-out space-y-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">About</h2>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="report" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3 cursor-pointer">
|
||||
<TabsTrigger value="report" className="cursor-pointer transition-colors hover:text-primary data-[state=active]:text-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none">Report Issue</TabsTrigger>
|
||||
<TabsTrigger value="faq" className="cursor-pointer transition-colors hover:text-primary data-[state=active]:text-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none">FAQ</TabsTrigger>
|
||||
<TabsTrigger value="projects" className="cursor-pointer transition-colors hover:text-primary data-[state=active]:text-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none">Other Projects</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="report" className="mt-4">
|
||||
<Card>
|
||||
<CardContent className="space-y-4 pt-4">
|
||||
<Tabs value={reportType} onValueChange={setReportType} className="w-full">
|
||||
<TabsList className="w-full grid grid-cols-2 cursor-pointer pb-2">
|
||||
<TabsTrigger value="bug" className="flex items-center gap-2 cursor-pointer transition-colors hover:text-primary data-[state=active]:text-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"><Bug className="h-4 w-4" /> Bug Report</TabsTrigger>
|
||||
<TabsTrigger value="feature" className="flex items-center gap-2 cursor-pointer transition-colors hover:text-primary data-[state=active]:text-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"><Lightbulb className="h-4 w-4" /> Feature Request</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="mt-4">
|
||||
{reportType === "bug" ? (<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2 flex flex-col">
|
||||
<Label>Problem</Label>
|
||||
<Textarea className="flex-1 resize-none" placeholder="Describe the problem..." value={problem} onChange={e => setProblem(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Type</Label>
|
||||
<ToggleGroup type="single" value={bugType} onValueChange={(val) => {
|
||||
if (val)
|
||||
setBugType(val);
|
||||
}} className="justify-start w-full cursor-pointer">
|
||||
<ToggleGroupItem value="Track" className="flex-1 cursor-pointer" aria-label="Toggle track">
|
||||
Track
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="Album" className="flex-1 cursor-pointer" aria-label="Toggle album">
|
||||
Album
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="Playlist" className="flex-1 cursor-pointer" aria-label="Toggle playlist">
|
||||
Playlist
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="Artist" className="flex-1 cursor-pointer" aria-label="Toggle artist">
|
||||
Artist
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Spotify URL</Label>
|
||||
<Input placeholder="https://open.spotify.com/..." value={spotifyUrl} onChange={e => setSpotifyUrl(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-2 h-full">
|
||||
<Label>Additional Context</Label>
|
||||
<Textarea className="h-[125px] resize-none" placeholder="Any other details? Screenshots or recordings are very helpful (please upload directly to GitHub)." value={bugContext} onChange={e => setBugContext(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>) : (<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2 flex flex-col">
|
||||
<Label>Description</Label>
|
||||
<Textarea className="flex-1 resize-none" placeholder="Describe your feature request..." value={featureDesc} onChange={e => setFeatureDesc(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Use Case</Label>
|
||||
<Textarea className="h-[100px] resize-none" placeholder="How would this feature be useful?" value={useCase} onChange={e => setUseCase(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Additional Context</Label>
|
||||
<Textarea className="h-[135px] resize-none" placeholder="Any other details? Screenshots/recordings or examples..." value={featureContext} onChange={e => setFeatureContext(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>)}
|
||||
</div>
|
||||
</Tabs>
|
||||
|
||||
<div className="flex justify-center pt-2">
|
||||
<Button className="w-[200px] cursor-pointer" onClick={handleSubmit}>
|
||||
<ExternalLink className="h-4 w-4" /> Create Issue on GitHub
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="faq" className="mt-4 space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Frequently Asked Questions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{faqs.map((faq, index) => (<div key={index} className="space-y-2">
|
||||
<h3 className="font-medium text-base text-foreground/90">{faq.q}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">{faq.a}</p>
|
||||
</div>))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="projects" className="mt-4 space-y-4">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card className="hover:bg-muted/50 hover:border-primary/50 transition-colors cursor-pointer" onClick={() => openExternal("https://exyezed.cc/")}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><img src={ExyezedIcon} className="h-5 w-5 invert dark:invert-0" alt="exyezed" /> exyezed.cc</CardTitle>
|
||||
<CardDescription>Browser Extensions & Scripts</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="hover:bg-muted/50 hover:border-primary/50 transition-colors cursor-pointer" onClick={() => openExternal("https://spotubedl.com/")}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><img src={SpotubeDLIcon} className="h-5 w-5" alt="SpotubeDL" /> SpotubeDL</CardTitle>
|
||||
<CardDescription>Download Spotify Tracks, Albums, Playlists as MP3/OGG/Opus with High Quality.</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="hover:bg-muted/50 hover:border-primary/50 transition-colors cursor-pointer" onClick={() => openExternal("https://github.com/afkarxyz/SpotiDownloader")}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><img src={SpotiDownloaderIcon} className="h-5 w-5" alt="SpotiDownloader" /> SpotiDownloader</CardTitle>
|
||||
<CardDescription>Get Spotify tracks in MP3 and FLAC via the spotidownloader.com API.</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="hover:bg-muted/50 hover:border-primary/50 transition-colors cursor-pointer" onClick={() => openExternal("https://github.com/afkarxyz/Twitter-X-Media-Batch-Downloader")}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><img src={XBatchDLIcon} className="h-5 w-5" alt="Twitter/X Media Batch Downloader" /> Twitter/X Batch Downloader</CardTitle>
|
||||
<CardDescription>A GUI tool to download original-quality images and videos from Twitter/X accounts, powered by gallery-dl by @mikf</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>);
|
||||
}
|
||||
Reference in New Issue
Block a user