"use client"
import NewProjectModal from "@/components/dashboard/newProject"
import ProjectCard from "@/components/dashboard/projectCard/"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardTitle,
} from "@/components/ui/card"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { deleteSandbox, updateSandbox, updateUser } from "@/lib/actions"
import { socialIcons } from "@/lib/data"
import { editUserSchema, EditUserSchema } from "@/lib/schema"
import { TIERS } from "@/lib/tiers"
import { SandboxWithLiked, User, UserLink } from "@/lib/types"
import { cn, parseSocialLink } from "@/lib/utils"
import { useUser } from "@clerk/nextjs"
import { zodResolver } from "@hookform/resolvers/zod"
import {
Edit,
Globe,
Heart,
Info,
Loader2,
LucideIcon,
Package2,
PlusCircle,
Sparkles,
Trash2,
X,
} from "lucide-react"
import { useRouter } from "next/navigation"
import {
Fragment,
useCallback,
useEffect,
useMemo,
useRef,
useState,
useTransition,
} from "react"
import { useFormState, useFormStatus } from "react-dom"
import { useFieldArray, useForm } from "react-hook-form"
import { toast } from "sonner"
import Avatar from "../ui/avatar"
import { Badge } from "../ui/badge"
import { Input } from "../ui/input"
import { Progress } from "../ui/progress"
import { Textarea } from "../ui/textarea"
// #region Profile Page
export default function ProfilePage({
publicSandboxes,
privateSandboxes,
profileOwner,
loggedInUser,
}: {
publicSandboxes: SandboxWithLiked[]
privateSandboxes: SandboxWithLiked[]
profileOwner: User
loggedInUser: User | null
}) {
const isOwnProfile = profileOwner.id === loggedInUser?.id
const sandboxes = useMemo(() => {
const allSandboxes = isOwnProfile
? [...publicSandboxes, ...privateSandboxes]
: publicSandboxes
return allSandboxes
}, [isOwnProfile, publicSandboxes, privateSandboxes])
return (
<>
>
)
}
// #endregion
// #region Profile Card
function ProfileCard({
name,
username,
avatarUrl,
sandboxes,
joinedDate,
generations,
isOwnProfile,
bio,
personalWebsite,
socialLinks = [],
tier,
}: {
name: string
username: string
avatarUrl: string | null
bio: string | null
personalWebsite: string | null
socialLinks: UserLink[]
sandboxes: SandboxWithLiked[]
joinedDate: Date
generations?: number
isOwnProfile: boolean
tier: string
}) {
const { user } = useUser()
const [isEditing, setIsEditing] = useState(false)
const joinedAt = useMemo(() => {
const date = new Date(joinedDate).toLocaleDateString("en-US", {
month: "long",
year: "numeric",
})
return `Joined ${date}`
}, [joinedDate])
const toggleEdit = useCallback(() => {
setIsEditing((s) => !s)
}, [])
const stats = useMemo(() => {
const totalSandboxes = sandboxes.length
const totalLikes = sandboxes.reduce(
(sum, sandbox) => sum + sandbox.likeCount,
0
)
return {
sandboxes:
totalSandboxes === 1 ? "1 sandbox" : `${totalSandboxes} sandboxes`,
likes: totalLikes === 1 ? "1 like" : `${totalLikes} likes`,
}
}, [sandboxes])
const showAddMoreInfoBanner = useMemo(() => {
return !bio && !personalWebsite && (socialLinks?.length ?? 0) === 0
}, [personalWebsite, bio, socialLinks])
return (
{isOwnProfile && (
{isEditing ? (
) : showAddMoreInfoBanner ? (
<>
>
) : (
)}
{showAddMoreInfoBanner
? "Add more information to your profile"
: "Edit your profile"}
)}
{isEditing ? (
) : (
{name}
{`@${username}`}
{typeof generations === "number" && (
)}
{bio &&
{bio}
}
{(socialLinks.length > 0 || personalWebsite) && (
)}
{joinedAt}
)}
)
}
function EditProfileForm(props: {
name: string
username: string
avatarUrl: string | null
bio: string | null
personalWebsite: string | null
socialLinks: UserLink[]
toggleEdit: () => void
}) {
const router = useRouter()
const { user } = useUser()
const formRef = useRef(null)
const [formState, formAction] = useFormState(updateUser, {
message: "",
})
const [isPending, startTransition] = useTransition()
const { name, username, bio, personalWebsite, socialLinks, toggleEdit } =
props
const form = useForm({
resolver: zodResolver(editUserSchema),
defaultValues: {
oldUsername: username,
id: user?.id,
name,
username,
bio: bio ?? "",
personalWebsite: personalWebsite ?? "",
links:
socialLinks.length > 0
? socialLinks
: [{ url: "", platform: "generic" }],
...(formState.fields ?? {}),
},
})
const { fields, append, remove } = useFieldArray({
name: "links",
control: form.control,
})
useEffect(() => {
const message = formState.message
if (!Boolean(message)) return
if ("error" in formState) {
toast.error(formState.message)
return
}
toast.success(formState.message as String)
toggleEdit()
if (formState?.newRoute) {
router.replace(formState.newRoute)
}
}, [formState])
return (
)
}
function SubmitButton({ isPending }: { isPending: boolean }) {
const formStatus = useFormStatus()
const { pending } = formStatus
const pend = pending || isPending
return (
{pend && }
Save Changes
)
}
// #endregion
// #region Sandboxes Panel
function SandboxesPanel({
publicSandboxes,
privateSandboxes,
isOwnProfile,
}: {
publicSandboxes: SandboxWithLiked[]
privateSandboxes: SandboxWithLiked[]
isOwnProfile: boolean
}) {
const [deletingId, setDeletingId] = useState("")
const hasPublicSandboxes = publicSandboxes.length > 0
const hasPrivateSandboxes = privateSandboxes.length > 0
const onVisibilityChange = useMemo(
() =>
async (sandbox: Pick) => {
const newVisibility =
sandbox.visibility === "public" ? "private" : "public"
toast(`Project ${sandbox.name} is now ${newVisibility}.`)
await updateSandbox({
id: sandbox.id,
visibility: newVisibility,
})
},
[]
)
const onDelete = useMemo(
() => async (sandbox: Pick) => {
setDeletingId(sandbox.id)
toast(`Project ${sandbox.name} deleted.`)
await deleteSandbox(sandbox.id)
setDeletingId("")
},
[]
)
if (!isOwnProfile) {
return (
{hasPublicSandboxes ? (
<>
Sandboxes
{publicSandboxes.map((sandbox) => {
return (
{isOwnProfile ? (
) : (
)}
)
})}
>
) : (
)}
)
}
return (
Public
Private
{hasPublicSandboxes ? (
{publicSandboxes.map((sandbox) => {
return (
{isOwnProfile ? (
) : (
)}
)
})}
) : (
)}
{hasPrivateSandboxes ? (
{privateSandboxes.map((sandbox) => (
))}
) : (
)}
)
}
// #endregion
// #region Empty State
function EmptyState({
type,
isOwnProfile,
}: {
type: "public" | "private"
isOwnProfile: boolean
}) {
const [newProjectModalOpen, setNewProjectModalOpen] = useState(false)
const text = useMemo(() => {
let title: string
let description: string
switch (type) {
case "public":
title = "No public sandboxes yet"
description = isOwnProfile
? "Create your first public sandbox to share your work with the world!"
: "user has no public sandboxes"
case "private":
title = "No private sandboxes yet"
description = isOwnProfile
? "Create your first private sandbox to start working on your personal projects!"
: "user has no private sandboxes"
}
return {
title,
description,
}
}, [type, isOwnProfile])
const openModal = useCallback(() => setNewProjectModalOpen(true), [])
return (
<>
{text.title}
{text.description}
{isOwnProfile && (
Create Sandbox
)}
>
)
}
// #endregion
// #region StatsItem
interface StatsItemProps {
icon: LucideIcon
label: string
}
const StatsItem = ({ icon: Icon, label }: StatsItemProps) => (
{label}
)
// #endregion
// #region Sub Badge
const SubscriptionBadge = ({
generations,
tier = "FREE",
}: {
generations: number
tier?: keyof typeof TIERS
}) => {
return (
{tier}
AI Generations
{`${generations} / ${TIERS[tier].generations}`}
Upgrade to Pro
)
}
// #endregion