diff --git a/apps/web/src/components/admin/builder/social-tab.tsx b/apps/web/src/components/admin/builder/social-tab.tsx index a2fc457..b30aa1c 100644 --- a/apps/web/src/components/admin/builder/social-tab.tsx +++ b/apps/web/src/components/admin/builder/social-tab.tsx @@ -11,6 +11,15 @@ import { getAccessibleIconFill, isLowLuminance } from "@linkden/ui/color-contras import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; import { SectionHeader } from "@/components/admin/section-header"; import { NetworkRow } from "@/components/admin/social/network-row"; import { @@ -19,6 +28,42 @@ import { } from "@/components/admin/social/social-constants"; import { cn, getAdminThemeColors } from "@/lib/utils"; +const URL_PLACEHOLDERS: Record = { + twitter: "https://twitter.com/yourhandle", + x: "https://x.com/yourhandle", + instagram: "https://instagram.com/yourhandle", + facebook: "https://facebook.com/yourpage", + linkedin: "https://linkedin.com/in/yourhandle", + github: "https://github.com/yourhandle", + gitlab: "https://gitlab.com/yourhandle", + youtube: "https://youtube.com/@yourhandle", + tiktok: "https://tiktok.com/@yourhandle", + twitch: "https://twitch.tv/yourhandle", + discord: "https://discord.gg/yourinvite", + telegram: "https://t.me/yourhandle", + whatsapp: "https://wa.me/1234567890", + reddit: "https://reddit.com/user/yourhandle", + pinterest: "https://pinterest.com/yourhandle", + snapchat: "https://snapchat.com/add/yourhandle", + threads: "https://threads.net/@yourhandle", + mastodon: "https://mastodon.social/@yourhandle", + bluesky: "https://bsky.app/profile/yourhandle", + spotify: "https://open.spotify.com/artist/yourid", + "apple-music": "https://music.apple.com/artist/yourid", + soundcloud: "https://soundcloud.com/yourhandle", + bandcamp: "https://yourhandle.bandcamp.com", + "buy-me-a-coffee": "https://buymeacoffee.com/yourhandle", + patreon: "https://patreon.com/yourhandle", + kofi: "https://ko-fi.com/yourhandle", + behance: "https://behance.net/yourhandle", + dribbble: "https://dribbble.com/yourhandle", + medium: "https://medium.com/@yourhandle", + substack: "https://yourhandle.substack.com", + blogger: "https://yourblog.blogspot.com", + email: "mailto:you@example.com", + website: "https://yoursite.com", +}; + interface SocialTabProps { onDirtyChange: (dirty: boolean) => void; } @@ -33,6 +78,24 @@ export function SocialTab({ onDirtyChange }: SocialTabProps) { const [drafts, setDrafts] = useState>({}); const [socialInitialized, setSocialInitialized] = useState(false); const [searchQuery, setSearchQuery] = useState(""); + const [pendingSlug, setPendingSlug] = useState(null); + const [pendingUrl, setPendingUrl] = useState(""); + + const pendingBrand = useMemo( + () => (pendingSlug ? socialBrands.find((b) => b.slug === pendingSlug) ?? null : null), + [pendingSlug], + ); + + const pendingUrlValid = useMemo(() => { + const trimmed = pendingUrl.trim(); + if (!trimmed) return false; + try { + const u = new URL(trimmed); + return u.protocol === "http:" || u.protocol === "https:"; + } catch { + return false; + } + }, [pendingUrl]); const dbRows = socialsQuery.data ?? []; @@ -80,7 +143,7 @@ export function SocialTab({ onDirtyChange }: SocialTabProps) { } inactive.sort((a, b) => a.name.localeCompare(b.name)); - return inactive.slice(0, 20); + return inactive; }, [allItems, activeNetworks, searchQuery]); const socialDirty = useMemo(() => { @@ -124,12 +187,32 @@ export function SocialTab({ onDirtyChange }: SocialTabProps) { }); }; - const handleActivateNetwork = (slug: string) => { + const openAddDialog = (slug: string) => { + setPendingSlug(slug); + setPendingUrl(""); + }; + + const closeAddDialog = () => { + setPendingSlug(null); + setPendingUrl(""); + }; + + const handleConfirmAddNetwork = () => { + if (!pendingSlug || !pendingUrlValid) return; + const slug = pendingSlug; + const url = pendingUrl.trim(); setDrafts((prev) => ({ ...prev, - [slug]: { ...prev[slug], isActive: true }, + [slug]: { ...prev[slug], url, isActive: true }, })); setSearchQuery(""); + closeAddDialog(); + // Flush state, then scroll to new row + setTimeout(() => { + document + .getElementById(`network-${slug}`) + ?.scrollIntoView({ behavior: "smooth", block: "center" }); + }, 50); }; const handleRemoveNetwork = (slug: string) => { @@ -214,7 +297,7 @@ export function SocialTab({ onDirtyChange }: SocialTabProps) { + + + + + ); } diff --git a/bun.lock b/bun.lock index bb5aebb..bf4d0f6 100644 --- a/bun.lock +++ b/bun.lock @@ -11,13 +11,6 @@ }, "devDependencies": { "@cloudflare/workers-types": "^4.20260403.1", - "@codemirror/autocomplete": "^6.20.1", - "@codemirror/commands": "^6.10.3", - "@codemirror/lang-css": "^6.3.1", - "@codemirror/language": "^6.12.3", - "@codemirror/state": "^6.6.0", - "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.41.0", "@linkden/config": "workspace:*", "@tailwindcss/postcss": "^4.2.2", "@testing-library/jest-dom": "^6.9.1", @@ -25,13 +18,11 @@ "@types/node": "^25.5.0", "@vitejs/plugin-react": "^6.0.1", "bcryptjs": "^3.0.3", - "codemirror": "^6.0.2", "jsdom": "^28.1.0", "tailwindcss": "^4.2.2", "turbo": "^2.9.3", "typescript": "^5.9.3", "vitest": "^4.1.2", - "web": "workspace:*", }, }, "apps/docs": { @@ -94,11 +85,17 @@ "version": "0.1.0", "dependencies": { "@base-ui/react": "^1.3.0", + "@codemirror/autocomplete": "^6.20.1", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/language": "^6.12.3", + "@codemirror/state": "^6.6.0", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.41.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@headlessui/react": "^2.2.9", - "@libsql/client": "0.17.0", "@linkden/api": "workspace:*", "@linkden/auth": "workspace:*", "@linkden/env": "workspace:*", @@ -114,8 +111,7 @@ "better-auth": "^1.5.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "dotenv": "^17.3.1", - "libsql": "0.5.22", + "codemirror": "^6.0.2", "lucide-react": "^0.577.0", "next": "16.2.0", "next-themes": "^0.4.6",