@burdenoff/microfe-comradecircle 2026.704.1 → 2026.704.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comradecircle/components/Avatar.js +27 -15
- package/dist/comradecircle/components/Avatar.js.map +1 -1
- package/dist/comradecircle/components/AvatarUploadField.js +80 -0
- package/dist/comradecircle/components/AvatarUploadField.js.map +1 -0
- package/dist/comradecircle/components/CircleCard.js +2 -1
- package/dist/comradecircle/components/CircleCard.js.map +1 -1
- package/dist/comradecircle/components/CreateCircleDialog.js +74 -63
- package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -1
- package/dist/comradecircle/components/FileImage.js +20 -0
- package/dist/comradecircle/components/FileImage.js.map +1 -0
- package/dist/comradecircle/components/ImageAttachments.js +97 -0
- package/dist/comradecircle/components/ImageAttachments.js.map +1 -0
- package/dist/comradecircle/components/MemberIdentity.js +2 -1
- package/dist/comradecircle/components/MemberIdentity.js.map +1 -1
- package/dist/comradecircle/components/PostCard.js +7 -14
- package/dist/comradecircle/components/PostCard.js.map +1 -1
- package/dist/comradecircle/components/PostComposer.js +45 -35
- package/dist/comradecircle/components/PostComposer.js.map +1 -1
- package/dist/comradecircle/hooks/index.js +1 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.js +244 -241
- package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
- package/dist/comradecircle/hooks/useFiles.js +78 -0
- package/dist/comradecircle/hooks/useFiles.js.map +1 -0
- package/dist/comradecircle/lib/graphql-cache.js +9 -0
- package/dist/comradecircle/lib/graphql-cache.js.map +1 -0
- package/dist/comradecircle/pages/circles/CircleDetailPage.js +2 -1
- package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/members/MemberProfilePage.js +2 -1
- package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
- package/dist/comradecircle/pages/members/MembersPage.js +2 -1
- package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
- package/dist/comradecircle/pages/profile/MyProfilePage.js +100 -89
- package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MemberProfilePage.js","names":[],"sources":["../../../../src/comradecircle/pages/members/MemberProfilePage.tsx"],"sourcesContent":["/**\n * MemberProfilePage — a single member's public social profile.\n * @module comradecircle/pages/members\n *\n * Reads `comProfile(memberId)` (the social-identity layer). The visitor can\n * follow / unfollow the member (state derived from `comMyFollows('MEMBER')`);\n * on their own profile the action becomes an \"Edit profile\" shortcut. Interests\n * and skills render as token-only chips and hide when empty.\n */\n\nimport { ArrowLeft, UserX, UserPlus, UserCheck, Pencil } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { useComProfile, useComMyFollows, useComFollow, useComUnfollow } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { notify, describeError } from '../../lib/toast';\nimport { useParams } from 'react-router-dom';\n\nfunction ChipList({ items }: { items: string[] }) {\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {items.map((item) => (\n <span\n key={item}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {item}\n </span>\n ))}\n </div>\n );\n}\n\nexport function MemberProfilePage() {\n const { memberId } = useParams<{ memberId: string }>();\n const { navigateTo, currentUser } = useComradeCircle();\n const { data: profile, isLoading, isError, refetch } = useComProfile(memberId);\n\n const follows = useComMyFollows('MEMBER');\n const follow = useComFollow();\n const unfollow = useComUnfollow();\n\n const back = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('members')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <LoadingView label=\"Loading profile…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <ErrorView message=\"We couldn't load this member's profile.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n if (!profile) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <EmptyState\n icon={<UserX className=\"h-10 w-10\" />}\n title=\"No profile\"\n description=\"This member hasn't set up a ComradeCircle profile yet.\"\n />\n </PageLayout>\n );\n }\n\n const isSelf = Boolean(memberId && currentUser?.id === memberId);\n const isFollowing = (follows.data ?? []).some(\n (f) => f.targetId === memberId && f.targetType === 'MEMBER'\n );\n const followPending = follow.isPending || unfollow.isPending;\n\n const toggleFollow = async () => {\n if (!memberId) return;\n try {\n if (isFollowing) {\n await unfollow.mutateAsync({ targetId: memberId, targetType: 'MEMBER' });\n notify.success('Unfollowed', profile.displayName);\n } else {\n await follow.mutateAsync({ targetId: memberId, targetType: 'MEMBER' });\n notify.success('Following', profile.displayName);\n }\n } catch (err) {\n notify.error('Could not update follow', describeError(err));\n }\n };\n\n const action = isSelf ? (\n <Button variant=\"outline\" onClick={() => navigateTo('profile')}>\n <Pencil className=\"mr-1.5 h-4 w-4\" /> Edit profile\n </Button>\n ) : isFollowing ? (\n <Button variant=\"outline\" onClick={toggleFollow} disabled={followPending}>\n <UserCheck className=\"mr-1.5 h-4 w-4\" /> Following\n </Button>\n ) : (\n <Button onClick={toggleFollow} disabled={followPending}>\n <UserPlus className=\"mr-1.5 h-4 w-4\" /> Follow\n </Button>\n );\n\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <div className=\"mx-auto flex max-w-2xl flex-col gap-6\">\n {/* Identity header */}\n <div className=\"rounded-2xl border border-border-seam bg-bg-surface p-6\">\n <div className=\"flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex flex-col items-center gap-4 sm:flex-row sm:items-start\">\n <Avatar
|
|
1
|
+
{"version":3,"file":"MemberProfilePage.js","names":[],"sources":["../../../../src/comradecircle/pages/members/MemberProfilePage.tsx"],"sourcesContent":["/**\n * MemberProfilePage — a single member's public social profile.\n * @module comradecircle/pages/members\n *\n * Reads `comProfile(memberId)` (the social-identity layer). The visitor can\n * follow / unfollow the member (state derived from `comMyFollows('MEMBER')`);\n * on their own profile the action becomes an \"Edit profile\" shortcut. Interests\n * and skills render as token-only chips and hide when empty.\n */\n\nimport { ArrowLeft, UserX, UserPlus, UserCheck, Pencil } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { useComProfile, useComMyFollows, useComFollow, useComUnfollow } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { notify, describeError } from '../../lib/toast';\nimport { useParams } from 'react-router-dom';\n\nfunction ChipList({ items }: { items: string[] }) {\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {items.map((item) => (\n <span\n key={item}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {item}\n </span>\n ))}\n </div>\n );\n}\n\nexport function MemberProfilePage() {\n const { memberId } = useParams<{ memberId: string }>();\n const { navigateTo, currentUser } = useComradeCircle();\n const { data: profile, isLoading, isError, refetch } = useComProfile(memberId);\n\n const follows = useComMyFollows('MEMBER');\n const follow = useComFollow();\n const unfollow = useComUnfollow();\n\n const back = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('members')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <LoadingView label=\"Loading profile…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <ErrorView message=\"We couldn't load this member's profile.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n if (!profile) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <EmptyState\n icon={<UserX className=\"h-10 w-10\" />}\n title=\"No profile\"\n description=\"This member hasn't set up a ComradeCircle profile yet.\"\n />\n </PageLayout>\n );\n }\n\n const isSelf = Boolean(memberId && currentUser?.id === memberId);\n const isFollowing = (follows.data ?? []).some(\n (f) => f.targetId === memberId && f.targetType === 'MEMBER'\n );\n const followPending = follow.isPending || unfollow.isPending;\n\n const toggleFollow = async () => {\n if (!memberId) return;\n try {\n if (isFollowing) {\n await unfollow.mutateAsync({ targetId: memberId, targetType: 'MEMBER' });\n notify.success('Unfollowed', profile.displayName);\n } else {\n await follow.mutateAsync({ targetId: memberId, targetType: 'MEMBER' });\n notify.success('Following', profile.displayName);\n }\n } catch (err) {\n notify.error('Could not update follow', describeError(err));\n }\n };\n\n const action = isSelf ? (\n <Button variant=\"outline\" onClick={() => navigateTo('profile')}>\n <Pencil className=\"mr-1.5 h-4 w-4\" /> Edit profile\n </Button>\n ) : isFollowing ? (\n <Button variant=\"outline\" onClick={toggleFollow} disabled={followPending}>\n <UserCheck className=\"mr-1.5 h-4 w-4\" /> Following\n </Button>\n ) : (\n <Button onClick={toggleFollow} disabled={followPending}>\n <UserPlus className=\"mr-1.5 h-4 w-4\" /> Follow\n </Button>\n );\n\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <div className=\"mx-auto flex max-w-2xl flex-col gap-6\">\n {/* Identity header */}\n <div className=\"rounded-2xl border border-border-seam bg-bg-surface p-6\">\n <div className=\"flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex flex-col items-center gap-4 sm:flex-row sm:items-start\">\n <Avatar\n name={profile.displayName}\n presence={profile.presence}\n size=\"xl\"\n fileId={profile.avatar?.id}\n />\n <div className=\"min-w-0 text-center sm:text-left\">\n <h2 className=\"text-2xl font-semibold text-text-primary\">{profile.displayName}</h2>\n {profile.headline && (\n <p className=\"mt-1 text-sm text-text-secondary\">{profile.headline}</p>\n )}\n <div className=\"mt-3 flex justify-center sm:justify-start\">\n <StatusBadge status={profile.presence} />\n </div>\n </div>\n </div>\n <div className=\"flex shrink-0 justify-center sm:justify-end\">{action}</div>\n </div>\n\n {profile.bio && (\n <p className=\"mt-5 whitespace-pre-wrap text-sm text-text-secondary\">{profile.bio}</p>\n )}\n </div>\n\n {profile.interests.length > 0 && (\n <Section title=\"Interests\">\n <ChipList items={profile.interests} />\n </Section>\n )}\n\n {profile.skills.length > 0 && (\n <Section title=\"Skills\">\n <ChipList items={profile.skills} />\n </Section>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MembersPage.js","names":[],"sources":["../../../../src/comradecircle/pages/members/MembersPage.tsx"],"sourcesContent":["/**\n * MembersPage — the discoverable member directory.\n * @module comradecircle/pages/members\n *\n * Reads `comListProfiles(discoverable: true)` — every member who has opted into\n * discovery via their {@link ComProfile}. Each card resolves display identity\n * from the social-identity layer (never a raw actor id), links to the member's\n * public profile, and offers a Follow/Following toggle wired to `comFollow` /\n * `comUnfollow` (targetType MEMBER). An optional client-side search narrows the\n * current page by name, headline, skills, or interests.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Search, UserPlus, Check } from 'lucide-react';\nimport { GlassCard, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useComProfiles,\n useComMyFollows,\n useComFollow,\n useComUnfollow,\n useCursorPager,\n} from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { notify, describeError } from '../../lib/toast';\nimport type { ComProfile } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nexport function MembersPage() {\n const { navigateTo, currentUser } = useComradeCircle();\n const pager = useCursorPager();\n const [search, setSearch] = useState('');\n const [pendingId, setPendingId] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useComProfiles({\n discoverable: true,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const { data: follows } = useComMyFollows('MEMBER');\n const follow = useComFollow();\n const unfollow = useComUnfollow();\n\n const followSet = useMemo(() => new Set((follows ?? []).map((f) => f.targetId)), [follows]);\n\n const profiles = useMemo(() => data?.items ?? [], [data]);\n\n const visible = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return profiles;\n return profiles.filter((p) => {\n const haystack = [p.displayName, p.headline ?? '', ...p.skills, ...p.interests]\n .join(' ')\n .toLowerCase();\n return haystack.includes(q);\n });\n }, [profiles, search]);\n\n const toggleFollow = async (profile: ComProfile) => {\n const wasFollowing = followSet.has(profile.memberId);\n const input = { targetId: profile.memberId, targetType: 'MEMBER' as const };\n setPendingId(profile.memberId);\n try {\n if (wasFollowing) {\n await unfollow.mutateAsync(input);\n notify.success('Unfollowed', profile.displayName);\n } else {\n await follow.mutateAsync(input);\n notify.success('Following', profile.displayName);\n }\n } catch (err) {\n notify.error(wasFollowing ? 'Could not unfollow' : 'Could not follow', describeError(err));\n } finally {\n setPendingId(null);\n }\n };\n\n return (\n <PageLayout\n title=\"Members\"\n description=\"Discover and follow people across your circles — search by name, headline, or skills.\"\n >\n {isLoading ? (\n <LoadingView label=\"Loading members…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load the member directory.\" onRetry={() => refetch()} />\n ) : profiles.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No members to discover yet\"\n description=\"When members make their profiles discoverable, they'll show up here for you to follow.\"\n />\n ) : (\n <div className=\"flex flex-col gap-5\">\n <div className=\"relative w-full max-w-sm\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\"\n aria-hidden=\"true\"\n />\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by name, headline, or skills\"\n aria-label=\"Search members\"\n className=\"pl-9\"\n />\n </div>\n\n {visible.length === 0 ? (\n <EmptyState\n icon={<Search className=\"h-9 w-9\" />}\n title=\"No matches on this page\"\n description=\"No members on this page match your search. Try a different term or browse another page.\"\n />\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {visible.map((p) => {\n const isSelf = Boolean(currentUser?.id && p.memberId === currentUser.id);\n const isFollowing = followSet.has(p.memberId);\n const chips = Array.from(new Set([...p.skills, ...p.interests])).slice(0, 4);\n\n return (\n <GlassCard key={p.id} treatment=\"plain\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start gap-3\">\n <Avatar name={p.displayName} presence={p.presence} size=\"lg\" />\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`members/${p.memberId}`)}\n className=\"block max-w-full truncate text-left text-base font-semibold text-text-primary hover:underline\"\n >\n {p.displayName}\n </button>\n {p.headline && (\n <p className=\"truncate text-sm text-text-secondary\">{p.headline}</p>\n )}\n <div className=\"mt-1.5\">\n <StatusBadge status={p.presence} />\n </div>\n </div>\n </div>\n\n {chips.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {chips.map((chip) => (\n <span\n key={chip}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {chip}\n </span>\n ))}\n </div>\n )}\n\n {!isSelf && (\n <div className=\"mt-4 flex items-center justify-end border-t border-border-seam pt-3\">\n {isFollowing ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => toggleFollow(p)}\n disabled={pendingId === p.memberId}\n >\n <Check className=\"mr-1.5 h-4 w-4\" /> Following\n </Button>\n ) : (\n <Button\n size=\"sm\"\n onClick={() => toggleFollow(p)}\n disabled={pendingId === p.memberId}\n >\n <UserPlus className=\"mr-1.5 h-4 w-4\" /> Follow\n </Button>\n )}\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={profiles.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MembersPage.js","names":[],"sources":["../../../../src/comradecircle/pages/members/MembersPage.tsx"],"sourcesContent":["/**\n * MembersPage — the discoverable member directory.\n * @module comradecircle/pages/members\n *\n * Reads `comListProfiles(discoverable: true)` — every member who has opted into\n * discovery via their {@link ComProfile}. Each card resolves display identity\n * from the social-identity layer (never a raw actor id), links to the member's\n * public profile, and offers a Follow/Following toggle wired to `comFollow` /\n * `comUnfollow` (targetType MEMBER). An optional client-side search narrows the\n * current page by name, headline, skills, or interests.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Search, UserPlus, Check } from 'lucide-react';\nimport { GlassCard, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useComProfiles,\n useComMyFollows,\n useComFollow,\n useComUnfollow,\n useCursorPager,\n} from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { notify, describeError } from '../../lib/toast';\nimport type { ComProfile } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nexport function MembersPage() {\n const { navigateTo, currentUser } = useComradeCircle();\n const pager = useCursorPager();\n const [search, setSearch] = useState('');\n const [pendingId, setPendingId] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useComProfiles({\n discoverable: true,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const { data: follows } = useComMyFollows('MEMBER');\n const follow = useComFollow();\n const unfollow = useComUnfollow();\n\n const followSet = useMemo(() => new Set((follows ?? []).map((f) => f.targetId)), [follows]);\n\n const profiles = useMemo(() => data?.items ?? [], [data]);\n\n const visible = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return profiles;\n return profiles.filter((p) => {\n const haystack = [p.displayName, p.headline ?? '', ...p.skills, ...p.interests]\n .join(' ')\n .toLowerCase();\n return haystack.includes(q);\n });\n }, [profiles, search]);\n\n const toggleFollow = async (profile: ComProfile) => {\n const wasFollowing = followSet.has(profile.memberId);\n const input = { targetId: profile.memberId, targetType: 'MEMBER' as const };\n setPendingId(profile.memberId);\n try {\n if (wasFollowing) {\n await unfollow.mutateAsync(input);\n notify.success('Unfollowed', profile.displayName);\n } else {\n await follow.mutateAsync(input);\n notify.success('Following', profile.displayName);\n }\n } catch (err) {\n notify.error(wasFollowing ? 'Could not unfollow' : 'Could not follow', describeError(err));\n } finally {\n setPendingId(null);\n }\n };\n\n return (\n <PageLayout\n title=\"Members\"\n description=\"Discover and follow people across your circles — search by name, headline, or skills.\"\n >\n {isLoading ? (\n <LoadingView label=\"Loading members…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load the member directory.\" onRetry={() => refetch()} />\n ) : profiles.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No members to discover yet\"\n description=\"When members make their profiles discoverable, they'll show up here for you to follow.\"\n />\n ) : (\n <div className=\"flex flex-col gap-5\">\n <div className=\"relative w-full max-w-sm\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\"\n aria-hidden=\"true\"\n />\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by name, headline, or skills\"\n aria-label=\"Search members\"\n className=\"pl-9\"\n />\n </div>\n\n {visible.length === 0 ? (\n <EmptyState\n icon={<Search className=\"h-9 w-9\" />}\n title=\"No matches on this page\"\n description=\"No members on this page match your search. Try a different term or browse another page.\"\n />\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {visible.map((p) => {\n const isSelf = Boolean(currentUser?.id && p.memberId === currentUser.id);\n const isFollowing = followSet.has(p.memberId);\n const chips = Array.from(new Set([...p.skills, ...p.interests])).slice(0, 4);\n\n return (\n <GlassCard key={p.id} treatment=\"plain\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start gap-3\">\n <Avatar\n name={p.displayName}\n presence={p.presence}\n size=\"lg\"\n fileId={p.avatar?.id}\n />\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`members/${p.memberId}`)}\n className=\"block max-w-full truncate text-left text-base font-semibold text-text-primary hover:underline\"\n >\n {p.displayName}\n </button>\n {p.headline && (\n <p className=\"truncate text-sm text-text-secondary\">{p.headline}</p>\n )}\n <div className=\"mt-1.5\">\n <StatusBadge status={p.presence} />\n </div>\n </div>\n </div>\n\n {chips.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {chips.map((chip) => (\n <span\n key={chip}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {chip}\n </span>\n ))}\n </div>\n )}\n\n {!isSelf && (\n <div className=\"mt-4 flex items-center justify-end border-t border-border-seam pt-3\">\n {isFollowing ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => toggleFollow(p)}\n disabled={pendingId === p.memberId}\n >\n <Check className=\"mr-1.5 h-4 w-4\" /> Following\n </Button>\n ) : (\n <Button\n size=\"sm\"\n onClick={() => toggleFollow(p)}\n disabled={pendingId === p.memberId}\n >\n <UserPlus className=\"mr-1.5 h-4 w-4\" /> Follow\n </Button>\n )}\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={profiles.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -9,54 +9,63 @@ import { formatEnumLabel as s } from "../../utils/formatters.js";
|
|
|
9
9
|
import { ErrorView as c, LoadingView as l } from "../../components/StateViews.js";
|
|
10
10
|
import { StatusBadge as u } from "../../components/StatusBadge.js";
|
|
11
11
|
import { TagInput as d } from "../../components/TagInput.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
12
|
+
import { AvatarUploadField as f } from "../../components/AvatarUploadField.js";
|
|
13
|
+
import { useState as p } from "react";
|
|
14
|
+
import { Save as m } from "lucide-react";
|
|
15
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
16
|
+
import { Button as _, GlassCard as v, Input as y, Label as b, Select as x, SelectContent as S, SelectItem as C, SelectTrigger as w, SelectValue as T, Switch as E, Textarea as D } from "@burdenoff/fe-libs/ui";
|
|
16
17
|
//#region src/comradecircle/pages/profile/MyProfilePage.tsx
|
|
17
|
-
var
|
|
18
|
+
var O = [
|
|
18
19
|
"ONLINE",
|
|
19
20
|
"AWAY",
|
|
20
21
|
"BUSY",
|
|
21
22
|
"OFFLINE"
|
|
22
23
|
];
|
|
23
|
-
function
|
|
24
|
+
function k() {
|
|
24
25
|
let { currentUser: r } = e(), i = r?.id, { data: a, isLoading: o, isError: s, refetch: u } = n(i), d = r?.name ?? r?.email ?? "";
|
|
25
|
-
return o ? /* @__PURE__ */
|
|
26
|
+
return o ? /* @__PURE__ */ h(t, {
|
|
26
27
|
title: "My Profile",
|
|
27
|
-
children: /* @__PURE__ */
|
|
28
|
-
}) : s ? /* @__PURE__ */
|
|
28
|
+
children: /* @__PURE__ */ h(l, { label: "Loading your profile…" })
|
|
29
|
+
}) : s ? /* @__PURE__ */ h(t, {
|
|
29
30
|
title: "My Profile",
|
|
30
|
-
children: /* @__PURE__ */
|
|
31
|
+
children: /* @__PURE__ */ h(c, {
|
|
31
32
|
title: "We couldn't load your profile",
|
|
32
33
|
message: "Something went wrong while fetching your profile. Retry before editing so you don't overwrite saved details.",
|
|
33
34
|
onRetry: () => u()
|
|
34
35
|
})
|
|
35
|
-
}) : /* @__PURE__ */
|
|
36
|
+
}) : /* @__PURE__ */ h(t, {
|
|
36
37
|
title: "My Profile",
|
|
37
38
|
description: "Your profile is your identity across every circle — it's how members recognise, find and follow you.",
|
|
38
|
-
children: /* @__PURE__ */
|
|
39
|
+
children: /* @__PURE__ */ h(A, {
|
|
39
40
|
initialProfile: a ?? null,
|
|
40
41
|
identityName: d
|
|
41
42
|
}, a?.id ?? "new")
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
|
-
function
|
|
45
|
-
let n = r(), [c, l] =
|
|
46
|
-
return /* @__PURE__ */
|
|
45
|
+
function A({ initialProfile: e, identityName: t }) {
|
|
46
|
+
let n = r(), [c, l] = p(e?.displayName ?? t), [k, A] = p(e?.headline ?? ""), [j, M] = p(e?.bio ?? ""), [N, P] = p(e?.interests ?? []), [F, I] = p(e?.skills ?? []), [L, R] = p(e?.presence ?? "ONLINE"), [z, B] = p(e?.discoverable ?? !0), [V, H] = p(e?.avatar?.id ?? null), U = c.trim();
|
|
47
|
+
return /* @__PURE__ */ g("div", {
|
|
47
48
|
className: "grid gap-6 lg:grid-cols-[1fr_320px]",
|
|
48
|
-
children: [/* @__PURE__ */
|
|
49
|
+
children: [/* @__PURE__ */ h(v, {
|
|
49
50
|
treatment: "plain",
|
|
50
51
|
className: "order-2 p-5 sm:p-6 lg:order-1",
|
|
51
|
-
children: /* @__PURE__ */
|
|
52
|
+
children: /* @__PURE__ */ g("div", {
|
|
52
53
|
className: "flex flex-col gap-5",
|
|
53
54
|
children: [
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ g("div", {
|
|
56
|
+
className: "grid gap-1.5",
|
|
57
|
+
children: [/* @__PURE__ */ h(b, { children: "Avatar" }), /* @__PURE__ */ h(f, {
|
|
58
|
+
fileId: V,
|
|
59
|
+
name: U || t,
|
|
60
|
+
onUploaded: H
|
|
61
|
+
})]
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ g("div", {
|
|
55
64
|
className: "grid gap-1.5",
|
|
56
|
-
children: [/* @__PURE__ */
|
|
65
|
+
children: [/* @__PURE__ */ h(b, {
|
|
57
66
|
htmlFor: "profile-name",
|
|
58
67
|
children: "Display name"
|
|
59
|
-
}), /* @__PURE__ */
|
|
68
|
+
}), /* @__PURE__ */ h(y, {
|
|
60
69
|
id: "profile-name",
|
|
61
70
|
value: c,
|
|
62
71
|
onChange: (e) => l(e.target.value),
|
|
@@ -65,94 +74,94 @@ function k({ initialProfile: e, identityName: t }) {
|
|
|
65
74
|
"aria-required": "true"
|
|
66
75
|
})]
|
|
67
76
|
}),
|
|
68
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ g("div", {
|
|
69
78
|
className: "grid gap-1.5",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
79
|
+
children: [/* @__PURE__ */ h(b, {
|
|
71
80
|
htmlFor: "profile-headline",
|
|
72
81
|
children: "Headline"
|
|
73
|
-
}), /* @__PURE__ */
|
|
82
|
+
}), /* @__PURE__ */ h(y, {
|
|
74
83
|
id: "profile-headline",
|
|
75
|
-
value:
|
|
76
|
-
onChange: (e) =>
|
|
84
|
+
value: k,
|
|
85
|
+
onChange: (e) => A(e.target.value),
|
|
77
86
|
placeholder: "e.g. Community organiser · Photographer"
|
|
78
87
|
})]
|
|
79
88
|
}),
|
|
80
|
-
/* @__PURE__ */
|
|
89
|
+
/* @__PURE__ */ g("div", {
|
|
81
90
|
className: "grid gap-1.5",
|
|
82
|
-
children: [/* @__PURE__ */
|
|
91
|
+
children: [/* @__PURE__ */ h(b, {
|
|
83
92
|
htmlFor: "profile-bio",
|
|
84
93
|
children: "Bio"
|
|
85
|
-
}), /* @__PURE__ */
|
|
94
|
+
}), /* @__PURE__ */ h(D, {
|
|
86
95
|
id: "profile-bio",
|
|
87
|
-
value:
|
|
88
|
-
onChange: (e) =>
|
|
96
|
+
value: j,
|
|
97
|
+
onChange: (e) => M(e.target.value),
|
|
89
98
|
rows: 4,
|
|
90
99
|
placeholder: "Tell circles a little about yourself."
|
|
91
100
|
})]
|
|
92
101
|
}),
|
|
93
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ g("div", {
|
|
94
103
|
className: "grid gap-1.5",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ h(b, {
|
|
96
105
|
htmlFor: "profile-interests",
|
|
97
106
|
children: "Interests"
|
|
98
|
-
}), /* @__PURE__ */
|
|
107
|
+
}), /* @__PURE__ */ h(d, {
|
|
99
108
|
id: "profile-interests",
|
|
100
|
-
value:
|
|
101
|
-
onChange:
|
|
109
|
+
value: N,
|
|
110
|
+
onChange: P,
|
|
102
111
|
placeholder: "Add an interest…"
|
|
103
112
|
})]
|
|
104
113
|
}),
|
|
105
|
-
/* @__PURE__ */
|
|
114
|
+
/* @__PURE__ */ g("div", {
|
|
106
115
|
className: "grid gap-1.5",
|
|
107
|
-
children: [/* @__PURE__ */
|
|
116
|
+
children: [/* @__PURE__ */ h(b, {
|
|
108
117
|
htmlFor: "profile-skills",
|
|
109
118
|
children: "Skills"
|
|
110
|
-
}), /* @__PURE__ */
|
|
119
|
+
}), /* @__PURE__ */ h(d, {
|
|
111
120
|
id: "profile-skills",
|
|
112
|
-
value:
|
|
113
|
-
onChange:
|
|
121
|
+
value: F,
|
|
122
|
+
onChange: I,
|
|
114
123
|
placeholder: "Add a skill…"
|
|
115
124
|
})]
|
|
116
125
|
}),
|
|
117
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ g("div", {
|
|
118
127
|
className: "grid gap-1.5",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
128
|
+
children: [/* @__PURE__ */ h(b, {
|
|
120
129
|
htmlFor: "profile-presence",
|
|
121
130
|
children: "Presence"
|
|
122
|
-
}), /* @__PURE__ */
|
|
123
|
-
value:
|
|
124
|
-
onValueChange: (e) =>
|
|
125
|
-
children: [/* @__PURE__ */
|
|
131
|
+
}), /* @__PURE__ */ g(x, {
|
|
132
|
+
value: L,
|
|
133
|
+
onValueChange: (e) => R(e),
|
|
134
|
+
children: [/* @__PURE__ */ h(w, {
|
|
126
135
|
id: "profile-presence",
|
|
127
|
-
children: /* @__PURE__ */
|
|
128
|
-
}), /* @__PURE__ */
|
|
136
|
+
children: /* @__PURE__ */ h(T, {})
|
|
137
|
+
}), /* @__PURE__ */ h(S, { children: O.map((e) => /* @__PURE__ */ h(C, {
|
|
129
138
|
value: e,
|
|
130
139
|
children: s(e)
|
|
131
140
|
}, e)) })]
|
|
132
141
|
})]
|
|
133
142
|
}),
|
|
134
|
-
/* @__PURE__ */
|
|
143
|
+
/* @__PURE__ */ g("div", {
|
|
135
144
|
className: "flex items-start justify-between gap-4 rounded-xl border border-border-seam bg-bg-surface p-3.5",
|
|
136
|
-
children: [/* @__PURE__ */
|
|
145
|
+
children: [/* @__PURE__ */ g("div", {
|
|
137
146
|
className: "min-w-0",
|
|
138
|
-
children: [/* @__PURE__ */
|
|
147
|
+
children: [/* @__PURE__ */ h(b, {
|
|
139
148
|
htmlFor: "profile-discoverable",
|
|
140
149
|
children: "List me in the member directory"
|
|
141
|
-
}), /* @__PURE__ */
|
|
150
|
+
}), /* @__PURE__ */ h("p", {
|
|
142
151
|
className: "mt-1 text-xs text-text-tertiary",
|
|
143
152
|
children: "When on, your profile appears in the discoverable member directory so others can find and follow you. Turn it off to stay unlisted."
|
|
144
153
|
})]
|
|
145
|
-
}), /* @__PURE__ */
|
|
154
|
+
}), /* @__PURE__ */ h(E, {
|
|
146
155
|
id: "profile-discoverable",
|
|
147
|
-
checked:
|
|
148
|
-
onCheckedChange:
|
|
156
|
+
checked: z,
|
|
157
|
+
onCheckedChange: B,
|
|
149
158
|
"aria-label": "List me in the member directory",
|
|
150
159
|
className: "mt-0.5 shrink-0"
|
|
151
160
|
})]
|
|
152
161
|
}),
|
|
153
|
-
/* @__PURE__ */
|
|
162
|
+
/* @__PURE__ */ h("div", {
|
|
154
163
|
className: "flex justify-end pt-1",
|
|
155
|
-
children: /* @__PURE__ */
|
|
164
|
+
children: /* @__PURE__ */ g(_, {
|
|
156
165
|
onClick: async () => {
|
|
157
166
|
let e = c.trim();
|
|
158
167
|
if (!e) {
|
|
@@ -162,85 +171,87 @@ function k({ initialProfile: e, identityName: t }) {
|
|
|
162
171
|
try {
|
|
163
172
|
await n.mutateAsync({
|
|
164
173
|
displayName: e,
|
|
165
|
-
headline:
|
|
166
|
-
bio:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
headline: k.trim() || null,
|
|
175
|
+
bio: j.trim() || null,
|
|
176
|
+
avatarFileId: V,
|
|
177
|
+
interests: N,
|
|
178
|
+
skills: F,
|
|
179
|
+
presence: L,
|
|
180
|
+
discoverable: z
|
|
171
181
|
}), o.success("Profile saved");
|
|
172
182
|
} catch (e) {
|
|
173
183
|
o.error("Could not save profile", a(e));
|
|
174
184
|
}
|
|
175
185
|
},
|
|
176
186
|
disabled: n.isPending,
|
|
177
|
-
children: [/* @__PURE__ */ m
|
|
187
|
+
children: [/* @__PURE__ */ h(m, { className: "mr-1.5 h-4 w-4" }), n.isPending ? "Saving…" : "Save profile"]
|
|
178
188
|
})
|
|
179
189
|
})
|
|
180
190
|
]
|
|
181
191
|
})
|
|
182
|
-
}), /* @__PURE__ */
|
|
192
|
+
}), /* @__PURE__ */ h("div", {
|
|
183
193
|
className: "order-1 lg:order-2",
|
|
184
|
-
children: /* @__PURE__ */
|
|
194
|
+
children: /* @__PURE__ */ g(v, {
|
|
185
195
|
treatment: "plain",
|
|
186
196
|
className: "p-5 lg:sticky lg:top-6",
|
|
187
197
|
children: [
|
|
188
|
-
/* @__PURE__ */
|
|
198
|
+
/* @__PURE__ */ h("p", {
|
|
189
199
|
className: "mb-4 text-xs font-semibold uppercase tracking-wide text-text-tertiary",
|
|
190
200
|
children: "Preview"
|
|
191
201
|
}),
|
|
192
|
-
/* @__PURE__ */
|
|
202
|
+
/* @__PURE__ */ g("div", {
|
|
193
203
|
className: "flex flex-col items-center gap-3 text-center",
|
|
194
204
|
children: [
|
|
195
|
-
/* @__PURE__ */
|
|
196
|
-
name:
|
|
205
|
+
/* @__PURE__ */ h(i, {
|
|
206
|
+
name: U || t,
|
|
197
207
|
size: "xl",
|
|
198
|
-
presence:
|
|
208
|
+
presence: L,
|
|
209
|
+
fileId: V
|
|
199
210
|
}),
|
|
200
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ g("div", {
|
|
201
212
|
className: "min-w-0",
|
|
202
|
-
children: [/* @__PURE__ */
|
|
213
|
+
children: [/* @__PURE__ */ h("h3", {
|
|
203
214
|
className: "truncate text-lg font-semibold text-text-primary",
|
|
204
|
-
children:
|
|
205
|
-
}),
|
|
215
|
+
children: U || "Your name"
|
|
216
|
+
}), k.trim() && /* @__PURE__ */ h("p", {
|
|
206
217
|
className: "mt-0.5 text-sm text-text-secondary",
|
|
207
|
-
children:
|
|
218
|
+
children: k.trim()
|
|
208
219
|
})]
|
|
209
220
|
}),
|
|
210
|
-
/* @__PURE__ */
|
|
221
|
+
/* @__PURE__ */ h(u, { status: L })
|
|
211
222
|
]
|
|
212
223
|
}),
|
|
213
|
-
|
|
224
|
+
j.trim() && /* @__PURE__ */ h("p", {
|
|
214
225
|
className: "mt-4 whitespace-pre-wrap break-words text-center text-sm text-text-secondary",
|
|
215
|
-
children:
|
|
226
|
+
children: j.trim()
|
|
216
227
|
}),
|
|
217
|
-
|
|
228
|
+
N.length > 0 && /* @__PURE__ */ g("div", {
|
|
218
229
|
className: "mt-5",
|
|
219
|
-
children: [/* @__PURE__ */
|
|
230
|
+
children: [/* @__PURE__ */ h("p", {
|
|
220
231
|
className: "mb-1.5 text-xs font-medium text-text-tertiary",
|
|
221
232
|
children: "Interests"
|
|
222
|
-
}), /* @__PURE__ */
|
|
233
|
+
}), /* @__PURE__ */ h("div", {
|
|
223
234
|
className: "flex flex-wrap gap-1.5",
|
|
224
|
-
children:
|
|
235
|
+
children: N.map((e) => /* @__PURE__ */ h("span", {
|
|
225
236
|
className: "rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
|
|
226
237
|
children: e
|
|
227
238
|
}, e))
|
|
228
239
|
})]
|
|
229
240
|
}),
|
|
230
|
-
|
|
241
|
+
F.length > 0 && /* @__PURE__ */ g("div", {
|
|
231
242
|
className: "mt-4",
|
|
232
|
-
children: [/* @__PURE__ */
|
|
243
|
+
children: [/* @__PURE__ */ h("p", {
|
|
233
244
|
className: "mb-1.5 text-xs font-medium text-text-tertiary",
|
|
234
245
|
children: "Skills"
|
|
235
|
-
}), /* @__PURE__ */
|
|
246
|
+
}), /* @__PURE__ */ h("div", {
|
|
236
247
|
className: "flex flex-wrap gap-1.5",
|
|
237
|
-
children:
|
|
248
|
+
children: F.map((e) => /* @__PURE__ */ h("span", {
|
|
238
249
|
className: "rounded-full bg-[var(--color-accent-soft)] px-2.5 py-0.5 text-xs font-medium text-text-primary",
|
|
239
250
|
children: e
|
|
240
251
|
}, e))
|
|
241
252
|
})]
|
|
242
253
|
}),
|
|
243
|
-
!
|
|
254
|
+
!z && /* @__PURE__ */ h("p", {
|
|
244
255
|
className: "mt-5 text-center text-xs text-text-tertiary",
|
|
245
256
|
children: "Hidden from the member directory."
|
|
246
257
|
})
|
|
@@ -250,6 +261,6 @@ function k({ initialProfile: e, identityName: t }) {
|
|
|
250
261
|
});
|
|
251
262
|
}
|
|
252
263
|
//#endregion
|
|
253
|
-
export {
|
|
264
|
+
export { k as MyProfilePage };
|
|
254
265
|
|
|
255
266
|
//# sourceMappingURL=MyProfilePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MyProfilePage.js","names":[],"sources":["../../../../src/comradecircle/pages/profile/MyProfilePage.tsx"],"sourcesContent":["/**\n * MyProfilePage — create or edit the caller's own social profile.\n * @module comradecircle/pages/profile\n *\n * A member's {@link ComProfile} is their identity across every circle — the\n * name, headline, presence and avatar initials that `MemberIdentity` resolves\n * everywhere a `memberId` appears. This page reads the caller's own profile\n * (`comProfile` by `currentUser.id`) and upserts it (`comUpsertProfile`). It\n * works both when a profile already exists (edit) and when none does yet\n * (create). The editor is only mounted once the profile query has settled and\n * seeds its fields via lazy state initialisers (keyed on the profile id), so no\n * state-syncing effect is needed. A live preview card mirrors the form.\n */\n\nimport { useState } from 'react';\nimport { Save } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n GlassCard,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { TagInput } from '../../components/TagInput';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComProfile, useComUpsertProfile } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatEnumLabel } from '../../utils';\nimport { notify, describeError } from '../../lib/toast';\nimport type { ComPresence, ComProfile } from '../../types';\n\nconst PRESENCE_OPTIONS: ComPresence[] = ['ONLINE', 'AWAY', 'BUSY', 'OFFLINE'];\n\nexport function MyProfilePage() {\n const { currentUser } = useComradeCircle();\n const memberId = currentUser?.id;\n const { data: profile, isLoading, isError, refetch } = useComProfile(memberId);\n\n // Seed create-mode from the shell identity so the name field is not blank.\n const identityName = currentUser?.name ?? currentUser?.email ?? '';\n\n if (isLoading) {\n return (\n <PageLayout title=\"My Profile\">\n <LoadingView label=\"Loading your profile…\" />\n </PageLayout>\n );\n }\n\n // A missing profile is NOT an error (the resolver returns null → create mode).\n // Only a genuine fetch failure lands here — surface it rather than silently\n // showing a blank form, which an upsert save could use to overwrite an\n // existing profile the caller never actually saw.\n if (isError) {\n return (\n <PageLayout title=\"My Profile\">\n <ErrorView\n title=\"We couldn't load your profile\"\n message=\"Something went wrong while fetching your profile. Retry before editing so you don't overwrite saved details.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"My Profile\"\n description=\"Your profile is your identity across every circle — it's how members recognise, find and follow you.\"\n >\n <ProfileEditor\n key={profile?.id ?? 'new'}\n initialProfile={profile ?? null}\n identityName={identityName}\n />\n </PageLayout>\n );\n}\n\ninterface ProfileEditorProps {\n initialProfile: ComProfile | null;\n identityName: string;\n}\n\nfunction ProfileEditor({ initialProfile, identityName }: ProfileEditorProps) {\n const upsert = useComUpsertProfile();\n\n const [displayName, setDisplayName] = useState(initialProfile?.displayName ?? identityName);\n const [headline, setHeadline] = useState(initialProfile?.headline ?? '');\n const [bio, setBio] = useState(initialProfile?.bio ?? '');\n const [interests, setInterests] = useState<string[]>(initialProfile?.interests ?? []);\n const [skills, setSkills] = useState<string[]>(initialProfile?.skills ?? []);\n const [presence, setPresence] = useState<ComPresence>(initialProfile?.presence ?? 'ONLINE');\n const [discoverable, setDiscoverable] = useState(initialProfile?.discoverable ?? true);\n\n const previewName = displayName.trim();\n\n const handleSave = async () => {\n const name = displayName.trim();\n if (!name) {\n notify.error('Display name required', 'Add a name so members can recognise you.');\n return;\n }\n try {\n await upsert.mutateAsync({\n displayName: name,\n headline: headline.trim() || null,\n bio: bio.trim() || null,\n interests,\n skills,\n presence,\n discoverable,\n });\n notify.success('Profile saved');\n } catch (err) {\n notify.error('Could not save profile', describeError(err));\n }\n };\n\n return (\n <div className=\"grid gap-6 lg:grid-cols-[1fr_320px]\">\n {/* Form */}\n <GlassCard treatment=\"plain\" className=\"order-2 p-5 sm:p-6 lg:order-1\">\n <div className=\"flex flex-col gap-5\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-name\">Display name</Label>\n <Input\n id=\"profile-name\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n placeholder=\"How members see you\"\n required\n aria-required=\"true\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-headline\">Headline</Label>\n <Input\n id=\"profile-headline\"\n value={headline}\n onChange={(e) => setHeadline(e.target.value)}\n placeholder=\"e.g. Community organiser · Photographer\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-bio\">Bio</Label>\n <Textarea\n id=\"profile-bio\"\n value={bio}\n onChange={(e) => setBio(e.target.value)}\n rows={4}\n placeholder=\"Tell circles a little about yourself.\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-interests\">Interests</Label>\n <TagInput\n id=\"profile-interests\"\n value={interests}\n onChange={setInterests}\n placeholder=\"Add an interest…\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-skills\">Skills</Label>\n <TagInput\n id=\"profile-skills\"\n value={skills}\n onChange={setSkills}\n placeholder=\"Add a skill…\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-presence\">Presence</Label>\n <Select value={presence} onValueChange={(v) => setPresence(v as ComPresence)}>\n <SelectTrigger id=\"profile-presence\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {PRESENCE_OPTIONS.map((p) => (\n <SelectItem key={p} value={p}>\n {formatEnumLabel(p)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-start justify-between gap-4 rounded-xl border border-border-seam bg-bg-surface p-3.5\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"profile-discoverable\">List me in the member directory</Label>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n When on, your profile appears in the discoverable member directory so others can\n find and follow you. Turn it off to stay unlisted.\n </p>\n </div>\n <Switch\n id=\"profile-discoverable\"\n checked={discoverable}\n onCheckedChange={setDiscoverable}\n aria-label=\"List me in the member directory\"\n className=\"mt-0.5 shrink-0\"\n />\n </div>\n\n <div className=\"flex justify-end pt-1\">\n <Button onClick={handleSave} disabled={upsert.isPending}>\n <Save className=\"mr-1.5 h-4 w-4\" />\n {upsert.isPending ? 'Saving…' : 'Save profile'}\n </Button>\n </div>\n </div>\n </GlassCard>\n\n {/* Live preview */}\n <div className=\"order-1 lg:order-2\">\n <GlassCard treatment=\"plain\" className=\"p-5 lg:sticky lg:top-6\">\n <p className=\"mb-4 text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Preview\n </p>\n <div className=\"flex flex-col items-center gap-3 text-center\">\n <Avatar name={previewName || identityName} size=\"xl\" presence={presence} />\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-lg font-semibold text-text-primary\">\n {previewName || 'Your name'}\n </h3>\n {headline.trim() && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">{headline.trim()}</p>\n )}\n </div>\n <StatusBadge status={presence} />\n </div>\n\n {bio.trim() && (\n <p className=\"mt-4 whitespace-pre-wrap break-words text-center text-sm text-text-secondary\">\n {bio.trim()}\n </p>\n )}\n\n {interests.length > 0 && (\n <div className=\"mt-5\">\n <p className=\"mb-1.5 text-xs font-medium text-text-tertiary\">Interests</p>\n <div className=\"flex flex-wrap gap-1.5\">\n {interests.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {skills.length > 0 && (\n <div className=\"mt-4\">\n <p className=\"mb-1.5 text-xs font-medium text-text-tertiary\">Skills</p>\n <div className=\"flex flex-wrap gap-1.5\">\n {skills.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-[var(--color-accent-soft)] px-2.5 py-0.5 text-xs font-medium text-text-primary\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {!discoverable && (\n <p className=\"mt-5 text-center text-xs text-text-tertiary\">\n Hidden from the member directory.\n </p>\n )}\n </GlassCard>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MyProfilePage.js","names":[],"sources":["../../../../src/comradecircle/pages/profile/MyProfilePage.tsx"],"sourcesContent":["/**\n * MyProfilePage — create or edit the caller's own social profile.\n * @module comradecircle/pages/profile\n *\n * A member's {@link ComProfile} is their identity across every circle — the\n * name, headline, presence and avatar initials that `MemberIdentity` resolves\n * everywhere a `memberId` appears. This page reads the caller's own profile\n * (`comProfile` by `currentUser.id`) and upserts it (`comUpsertProfile`). It\n * works both when a profile already exists (edit) and when none does yet\n * (create). The editor is only mounted once the profile query has settled and\n * seeds its fields via lazy state initialisers (keyed on the profile id), so no\n * state-syncing effect is needed. A live preview card mirrors the form.\n */\n\nimport { useState } from 'react';\nimport { Save } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n GlassCard,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { AvatarUploadField } from '../../components/AvatarUploadField';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { TagInput } from '../../components/TagInput';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComProfile, useComUpsertProfile } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatEnumLabel } from '../../utils';\nimport { notify, describeError } from '../../lib/toast';\nimport type { ComPresence, ComProfile } from '../../types';\n\nconst PRESENCE_OPTIONS: ComPresence[] = ['ONLINE', 'AWAY', 'BUSY', 'OFFLINE'];\n\nexport function MyProfilePage() {\n const { currentUser } = useComradeCircle();\n const memberId = currentUser?.id;\n const { data: profile, isLoading, isError, refetch } = useComProfile(memberId);\n\n // Seed create-mode from the shell identity so the name field is not blank.\n const identityName = currentUser?.name ?? currentUser?.email ?? '';\n\n if (isLoading) {\n return (\n <PageLayout title=\"My Profile\">\n <LoadingView label=\"Loading your profile…\" />\n </PageLayout>\n );\n }\n\n // A missing profile is NOT an error (the resolver returns null → create mode).\n // Only a genuine fetch failure lands here — surface it rather than silently\n // showing a blank form, which an upsert save could use to overwrite an\n // existing profile the caller never actually saw.\n if (isError) {\n return (\n <PageLayout title=\"My Profile\">\n <ErrorView\n title=\"We couldn't load your profile\"\n message=\"Something went wrong while fetching your profile. Retry before editing so you don't overwrite saved details.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"My Profile\"\n description=\"Your profile is your identity across every circle — it's how members recognise, find and follow you.\"\n >\n <ProfileEditor\n key={profile?.id ?? 'new'}\n initialProfile={profile ?? null}\n identityName={identityName}\n />\n </PageLayout>\n );\n}\n\ninterface ProfileEditorProps {\n initialProfile: ComProfile | null;\n identityName: string;\n}\n\nfunction ProfileEditor({ initialProfile, identityName }: ProfileEditorProps) {\n const upsert = useComUpsertProfile();\n\n const [displayName, setDisplayName] = useState(initialProfile?.displayName ?? identityName);\n const [headline, setHeadline] = useState(initialProfile?.headline ?? '');\n const [bio, setBio] = useState(initialProfile?.bio ?? '');\n const [interests, setInterests] = useState<string[]>(initialProfile?.interests ?? []);\n const [skills, setSkills] = useState<string[]>(initialProfile?.skills ?? []);\n const [presence, setPresence] = useState<ComPresence>(initialProfile?.presence ?? 'ONLINE');\n const [discoverable, setDiscoverable] = useState(initialProfile?.discoverable ?? true);\n const [avatarFileId, setAvatarFileId] = useState<string | null>(\n initialProfile?.avatar?.id ?? null\n );\n\n const previewName = displayName.trim();\n\n const handleSave = async () => {\n const name = displayName.trim();\n if (!name) {\n notify.error('Display name required', 'Add a name so members can recognise you.');\n return;\n }\n try {\n await upsert.mutateAsync({\n displayName: name,\n headline: headline.trim() || null,\n bio: bio.trim() || null,\n avatarFileId,\n interests,\n skills,\n presence,\n discoverable,\n });\n notify.success('Profile saved');\n } catch (err) {\n notify.error('Could not save profile', describeError(err));\n }\n };\n\n return (\n <div className=\"grid gap-6 lg:grid-cols-[1fr_320px]\">\n {/* Form */}\n <GlassCard treatment=\"plain\" className=\"order-2 p-5 sm:p-6 lg:order-1\">\n <div className=\"flex flex-col gap-5\">\n <div className=\"grid gap-1.5\">\n <Label>Avatar</Label>\n <AvatarUploadField\n fileId={avatarFileId}\n name={previewName || identityName}\n onUploaded={setAvatarFileId}\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-name\">Display name</Label>\n <Input\n id=\"profile-name\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n placeholder=\"How members see you\"\n required\n aria-required=\"true\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-headline\">Headline</Label>\n <Input\n id=\"profile-headline\"\n value={headline}\n onChange={(e) => setHeadline(e.target.value)}\n placeholder=\"e.g. Community organiser · Photographer\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-bio\">Bio</Label>\n <Textarea\n id=\"profile-bio\"\n value={bio}\n onChange={(e) => setBio(e.target.value)}\n rows={4}\n placeholder=\"Tell circles a little about yourself.\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-interests\">Interests</Label>\n <TagInput\n id=\"profile-interests\"\n value={interests}\n onChange={setInterests}\n placeholder=\"Add an interest…\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-skills\">Skills</Label>\n <TagInput\n id=\"profile-skills\"\n value={skills}\n onChange={setSkills}\n placeholder=\"Add a skill…\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"profile-presence\">Presence</Label>\n <Select value={presence} onValueChange={(v) => setPresence(v as ComPresence)}>\n <SelectTrigger id=\"profile-presence\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {PRESENCE_OPTIONS.map((p) => (\n <SelectItem key={p} value={p}>\n {formatEnumLabel(p)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-start justify-between gap-4 rounded-xl border border-border-seam bg-bg-surface p-3.5\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"profile-discoverable\">List me in the member directory</Label>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n When on, your profile appears in the discoverable member directory so others can\n find and follow you. Turn it off to stay unlisted.\n </p>\n </div>\n <Switch\n id=\"profile-discoverable\"\n checked={discoverable}\n onCheckedChange={setDiscoverable}\n aria-label=\"List me in the member directory\"\n className=\"mt-0.5 shrink-0\"\n />\n </div>\n\n <div className=\"flex justify-end pt-1\">\n <Button onClick={handleSave} disabled={upsert.isPending}>\n <Save className=\"mr-1.5 h-4 w-4\" />\n {upsert.isPending ? 'Saving…' : 'Save profile'}\n </Button>\n </div>\n </div>\n </GlassCard>\n\n {/* Live preview */}\n <div className=\"order-1 lg:order-2\">\n <GlassCard treatment=\"plain\" className=\"p-5 lg:sticky lg:top-6\">\n <p className=\"mb-4 text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Preview\n </p>\n <div className=\"flex flex-col items-center gap-3 text-center\">\n <Avatar\n name={previewName || identityName}\n size=\"xl\"\n presence={presence}\n fileId={avatarFileId}\n />\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-lg font-semibold text-text-primary\">\n {previewName || 'Your name'}\n </h3>\n {headline.trim() && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">{headline.trim()}</p>\n )}\n </div>\n <StatusBadge status={presence} />\n </div>\n\n {bio.trim() && (\n <p className=\"mt-4 whitespace-pre-wrap break-words text-center text-sm text-text-secondary\">\n {bio.trim()}\n </p>\n )}\n\n {interests.length > 0 && (\n <div className=\"mt-5\">\n <p className=\"mb-1.5 text-xs font-medium text-text-tertiary\">Interests</p>\n <div className=\"flex flex-wrap gap-1.5\">\n {interests.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {skills.length > 0 && (\n <div className=\"mt-4\">\n <p className=\"mb-1.5 text-xs font-medium text-text-tertiary\">Skills</p>\n <div className=\"flex flex-wrap gap-1.5\">\n {skills.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-[var(--color-accent-soft)] px-2.5 py-0.5 text-xs font-medium text-text-primary\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {!discoverable && (\n <p className=\"mt-5 text-center text-xs text-text-tertiary\">\n Hidden from the member directory.\n </p>\n )}\n </GlassCard>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|