Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,359 changes: 4,863 additions & 1,496 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@distube/ytdl-core": "^4.16.12",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource/geist-mono": "^5.2.7",
"@fontsource/geist-sans": "^5.2.5",
"@google/genai": "^0.13.0",
Expand Down
10,379 changes: 10,379 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ const Profile = lazy(() => import("@/pages/Profile"));
const Blogs = lazy(() => import("@/pages/Blogs"));
const BlogView = lazy(() => import("@/pages/BlogView"));
const NativeApplication = lazy(() => import("@/pages/NativeApplication"));
const CreateCreatorPackPage = lazy(() => import("@/pages/CreateCreatorPackPage"));
const ManageCreatorPacksPage = lazy(() => import("@/pages/ManageCreatorPacksPage"));
const EditCreatorPackPage = lazy(() => import("@/pages/EditCreatorPackPage"));
const CreatorPackPage = lazy(() => import("@/pages/CreatorPackPage"));

const LoadingFallback = ({ message = "Loading..." }: { message?: string }) => (
<div className="flex flex-col items-center justify-center min-h-screen gap-4">
Expand Down Expand Up @@ -153,6 +157,10 @@ const App = () => {
<Route path="/blogs" element={<Blogs />} />
<Route path="/blogs/:slug" element={<BlogView />} />
<Route path="/native-application" element={<NativeApplication />} />
<Route path="/creator-packs/new" element={<CreateCreatorPackPage />} />
<Route path="/creator-packs/manage" element={<ManageCreatorPacksPage />} />
<Route path="/creator-packs/:slug/edit" element={<EditCreatorPackPage />} />
<Route path="/creator-packs/:slug" element={<CreatorPackPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
Expand Down
10 changes: 10 additions & 0 deletions src/components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ const Hero = () => {
animate="visible"
variants={containerVariants}
>
<motion.div variants={itemVariants} className="mb-6 flex justify-center">
<Link
to="/resources?tab=creator-packs"
className="inline-flex items-center rounded-full border border-cow-purple/30 bg-cow-purple/10 px-4 py-1.5 text-sm font-medium text-cow-purple transition-colors hover:bg-cow-purple/20 font-geist"
>
<span className="mr-2 px-2 py-0.5 rounded-full bg-cow-purple text-white text-xs font-bold">NEW</span>
Creator Packs are here! <IconArrowRight className="ml-2 h-4 w-4" />
</Link>
</motion.div>

<motion.h1
className="text-4xl md:text-6xl font-bold mb-6 text-foreground dark:text-white leading-tight"
style={{
Expand Down
34 changes: 12 additions & 22 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,14 @@ const mainLinks: (NavLink | NavDropdown)[] = [
name: "Resources",
icon: "resources",
links: [
{ name: "Resources Hub", path: "/resources", icon: "resources-hub" },
{ name: "Resources Hub", path: "/resources", icon: "resources-hub", tag: "UPDATE" },
{ name: "Utilities", path: "/utilities", icon: "software" },
{
name: "Community Assets",
path: "/showcase",
icon: "yt-videos",
tag: "NEW",
},
{ name: "Community", path: "/community", icon: "yt-videos" },
{ name: "Native Application", path: "/native-application", icon: "software", tag: "NEW" },
],
},
{
name: "Tools",
icon: "tools",
icon: "tools", // You can use "tools" or any appropriate icon name
links: [
{ name: "Music Copyright Checker", path: "/gappa", icon: "music" },
{
Expand Down Expand Up @@ -215,17 +208,15 @@ const Navbar = () => {
return (
<>
<header
className={`fixed w-full z-50 transition-all duration-300 py-4 ${
scrolled ? "shadow-lg" : ""
}`}
className={`fixed w-full z-50 transition-all duration-300 py-4 ${scrolled ? "shadow-lg" : ""
}`}
style={{ top: 0 }}
>
<div
className={`absolute inset-0 z-[-1] pointer-events-none transition-all duration-300 ${
isTransparent
? "bg-transparent"
: "bg-gradient-to-r from-background/80 via-background/90 to-background/80 dark:from-background/80 dark:via-background/90 dark:to-background/80"
}`}
className={`absolute inset-0 z-[-1] pointer-events-none transition-all duration-300 ${isTransparent
? "bg-transparent"
: "bg-gradient-to-r from-background/80 via-background/90 to-background/80 dark:from-background/80 dark:via-background/90 dark:to-background/80"
}`}
style={getBackgroundStyle()}
/>
<div className="container mx-auto px-4 flex justify-between items-center relative z-10">
Expand Down Expand Up @@ -383,11 +374,10 @@ const Navbar = () => {
<span>{link.name}</span>
</div>
<IconChevronDown
className={`w-4 h-4 transition-transform duration-300 ${
openMobileCollapsible === link.name
? "rotate-180"
: ""
}`}
className={`w-4 h-4 transition-transform duration-300 ${openMobileCollapsible === link.name
? "rotate-180"
: ""
}`}
/>
</CollapsibleTrigger>
<CollapsibleContent className="animate-accordion-down">
Expand Down
175 changes: 175 additions & 0 deletions src/components/admin/AdminCreatorPacksManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React, { useEffect, useState } from 'react';
import { useCreatorPacks } from '@/hooks/useCreatorPacks';
import { Button } from '@/components/ui/button';
import { IconCheck, IconX, IconPackage, IconExternalLink } from '@tabler/icons-react';
import { formatDistanceToNow } from 'date-fns';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea";

const AdminCreatorPacksManager = () => {
const { fetchPendingPacks, reviewPack } = useCreatorPacks();
const [pendingPacks, setPendingPacks] = useState<any[]>([]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
const [isLoading, setIsLoading] = useState(true);
const [rejectionReason, setRejectionReason] = useState("");
const [selectedPackId, setSelectedPackId] = useState<string | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);

useEffect(() => {
loadPendingPacks();
}, []);

const loadPendingPacks = async () => {
setIsLoading(true);
const packs = await fetchPendingPacks();
setPendingPacks(packs);
setIsLoading(false);
};

const handleApprove = async (id: string) => {
const approvedPack = await reviewPack(id, 'approved');
if (approvedPack) {
setPendingPacks(prev => prev.filter(p => p.id !== id));
}
};

const handleReject = async () => {
if (!selectedPackId) return;

const rejectedPack = await reviewPack(selectedPackId, 'rejected', rejectionReason);
if (rejectedPack) {
setPendingPacks(prev => prev.filter(p => p.id !== selectedPackId));
setIsDialogOpen(false);
setRejectionReason("");
setSelectedPackId(null);
}
};

const openRejectDialog = (id: string) => {
setSelectedPackId(id);
setRejectionReason("");
setIsDialogOpen(true);
};

if (isLoading) {
return <div className="animate-pulse space-y-4">
<div className="h-20 bg-muted/20 rounded-xl" />
<div className="h-20 bg-muted/20 rounded-xl" />
</div>;
}

return (
<section className="mt-12 bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6 relative overflow-hidden">
<div className="absolute top-0 right-0 w-64 h-64 bg-cow-purple/5 rounded-full blur-3xl -z-10 transform translate-x-1/2 -translate-y-1/2" />

<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<IconPackage className="h-6 w-6 text-cow-purple" />
<h2 className="text-2xl font-vt323">Pending Creator Packs</h2>
</div>
<div className="bg-muted px-3 py-1 rounded-full text-sm font-medium">
{pendingPacks.length} Pending
</div>
</div>

{pendingPacks.length === 0 ? (
<div className="text-center py-12 text-muted-foreground bg-muted/10 rounded-lg border border-dashed border-border/50">
<IconPackage className="mx-auto h-8 w-8 mb-2 opacity-50" />
<p>No creator packs are pending review.</p>
</div>
) : (
<div className="space-y-4">
{pendingPacks.map(pack => (
<div key={pack.id} className="flex flex-col md:flex-row md:items-center justify-between p-4 bg-background border border-border/50 rounded-lg hover:border-cow-purple/30 transition-colors">
<div className="flex-1 min-w-0 mb-4 md:mb-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-medium text-lg truncate">{pack.title}</h3>
<span className="text-xs text-muted-foreground">
by {pack.profiles?.username || 'Unknown'}
</span>
</div>
<p className="text-sm text-muted-foreground line-clamp-1 mb-2">
{pack.small_description}
</p>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span>Submitted {formatDistanceToNow(new Date(pack.created_at), { addSuffix: true })}</span>
{pack.external_link && (
<a href={pack.external_link} target="_blank" rel="noopener noreferrer" className="flex items-center hover:text-cow-purple transition-colors">
<IconExternalLink className="w-3 h-3 mr-1" />
View Source
</a>
)}
</div>
</div>

<div className="flex items-center gap-2 md:pl-4">
<Button
size="sm"
variant="outline"
className="border-green-500/30 hover:bg-green-500/10 text-green-500"
onClick={() => handleApprove(pack.id)}
>
<IconCheck className="w-4 h-4 mr-1" />
Approve
</Button>
<Button
size="sm"
variant="outline"
className="border-red-500/30 hover:bg-red-500/10 text-red-500"
onClick={() => openRejectDialog(pack.id)}
>
<IconX className="w-4 h-4 mr-1" />
Reject
</Button>
</div>
</div>
))}
</div>
)}

<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="pixel-corners">
<DialogHeader>
<DialogTitle className="font-vt323 text-2xl">Reject Creator Pack</DialogTitle>
<DialogDescription>
Please provide a reason for rejecting this pack. The creator will see this message and can resubmit after making changes.
</DialogDescription>
</DialogHeader>

<div className="py-4">
<Textarea
placeholder="e.g., The external link is invalid, or the cover image violates our guidelines..."
value={rejectionReason}
onChange={(e) => setRejectionReason(e.target.value)}
className="pixel-input min-h-[100px]"
/>
</div>

<DialogFooter>
<Button variant="outline" onClick={() => setIsDialogOpen(false)} className="pixel-corners">
Cancel
</Button>
<Button
variant="destructive"
onClick={handleReject}
disabled={!rejectionReason.trim()}
className="pixel-corners"
>
Confirm Rejection
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

</section>
);
};

export default AdminCreatorPacksManager;
28 changes: 24 additions & 4 deletions src/components/auth/OAuthProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PixelSvgIcon from '../PixelSvgIcon';
import { useState } from 'react';

export const OAuthProviders = () => {
const { signInWithGitHub, signInWithDiscord } = useAuth();
const { signInWithGitHub, signInWithDiscord, signInWithGoogle } = useAuth();
const [loading, setLoading] = useState(false);

return (
Expand All @@ -20,10 +20,30 @@ export const OAuthProviders = () => {
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 sm:gap-4">
<Button
variant="outline"
className="pixel-btn-secondary"
className="pixel-btn-secondary w-full"
onClick={async () => {
setLoading(true);
const { error } = await signInWithGoogle();
if (error) {
toast.error(error);
setLoading(false);
}
}}
disabled={loading}
>
<img
src="/assets/google_icon.png"
alt="Google"
className="mr-2 h-5 w-5"
/>
Google
</Button>
<Button
variant="outline"
className="pixel-btn-secondary w-full"
onClick={async () => {
setLoading(true);
const { error } = await signInWithGitHub();
Expand All @@ -43,7 +63,7 @@ export const OAuthProviders = () => {
</Button>
<Button
variant="outline"
className="pixel-btn-secondary"
className="pixel-btn-secondary w-full"
onClick={async () => {
setLoading(true);
const { error } = await signInWithDiscord();
Expand Down
Loading