Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions www/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "./globals.css";
import { QueryClientProvider } from "@/providers/CustomQueryClientProvider";
import Script from "next/script";
import { Banner } from "@/components/banner";
import { Toaster } from "sonner";

const outfit = Outfit({
subsets: ["latin"],
Expand Down Expand Up @@ -72,6 +73,7 @@ export default function RootLayout({
className={`${outfit.variable} ${spaceGrotesk.variable} font-outfit`}
>
<QueryClientProvider>
<Toaster position="bottom-center" richColors />
<Banner />
{children}
</QueryClientProvider>
Expand Down
2 changes: 2 additions & 0 deletions www/components/ProfileSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Github, Globe, Linkedin, Twitter, User, BookOpen, Instagram } from "luc
import { ProfileSkeleton } from "@/components/skeletons/profile-skeleton";
import { addUserToSupabase, getUserProfile } from "@/lib/api";
import ClientResumeButton from "@/components/ClientResumeButton";
import ShareButton from "@/components/ShareButton";
import {
Tooltip,
TooltipContent,
Expand Down Expand Up @@ -173,6 +174,7 @@ export async function ProfileSection({
);
})}
<ClientResumeButton username={username} />
<ShareButton username={username} />
</div>
</div>

Expand Down
69 changes: 69 additions & 0 deletions www/components/ShareButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"use client";

import { useState, useEffect } from "react";
import { Share2, Check } from "lucide-react";
import { toast } from "sonner";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";

export default function ShareButton({ username }: { username: string }) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better readability and maintainability, it's a good practice to define component props using an interface or a type alias instead of an inline type.

interface ShareButtonProps {
  username: string;
}

export default function ShareButton({ username }: ShareButtonProps) {

const [copied, setCopied] = useState(false);
Comment thread
AnitusA marked this conversation as resolved.

const handleShare = async () => {
const shareData = {
title: `${username}'s Portfolio`,
text: `Check out ${username}'s professional developer portfolio on devb.io!`,
url: window.location.href,
};

if (navigator.share) {
try {
await navigator.share(shareData);
} catch (err) {
console.error("Error sharing:", err);
}
Comment on lines +25 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When using navigator.share, if the user cancels the share dialog, it throws a DOMException with the name AbortError. This is expected user behavior and not a technical error, so it shouldn't be logged to the console to avoid unnecessary noise.

Suggested change
} catch (err) {
console.error("Error sharing:", err);
}
} catch (err) {
if (!(err instanceof DOMException && err.name === "AbortError")) {
console.error("Error sharing:", err);
}
}

} else {
// Fallback: Copy to clipboard
try {
await navigator.clipboard.writeText(window.location.href);
setCopied(true);
toast.success("Link copied to clipboard!", {
style: {
backgroundColor: "#B9FF66",
color: "black",
border: "1px solid black",
fontWeight: "bold",
},
Comment thread
AnitusA marked this conversation as resolved.
});
setTimeout(() => setCopied(false), 2000);
Comment thread
AnitusA marked this conversation as resolved.
} catch (err) {
console.error("Failed to copy:", err);
}
}
};

return (
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleShare}
className="group relative w-12 h-12 flex items-center justify-center bg-white rounded-2xl border border-black hover:bg-[#B9FF66] transition-all duration-300 cursor-pointer"
>
<span className="w-6 h-6 group-hover:rotate-12 transition-transform duration-300">
{copied ? (
<Check size={24} className="text-green-600" />
) : (
<Share2 size={24} className="text-black" />
)}
</span>
</button>
</TooltipTrigger>
<TooltipContent>
{copied ? "Link Copied!" : "Share Portfolio"}
</TooltipContent>
</Tooltip>
);
}
1 change: 1 addition & 0 deletions www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-intersection-observer": "^9.15.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7",
"xml2js": "^0.6.2",
Expand Down
14 changes: 14 additions & 0 deletions www/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.