Add user tag editing and improve member error display
Build and Push Docker Image / build (push) Successful in 9m4s
Details
Build and Push Docker Image / build (push) Successful in 9m4s
Details
- Display actual error message in member detail page instead of generic Member not found - Add debug logging to user.get query to help diagnose issues - Add expertise tags editing for users in profile settings page - Update user.updateProfile mutation to accept expertiseTags Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f59cfd393b
commit
d7f0118940
|
|
@ -50,7 +50,7 @@ export default function MemberDetailPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userId = params.id as string
|
const userId = params.id as string
|
||||||
|
|
||||||
const { data: user, isLoading, refetch } = trpc.user.get.useQuery({ id: userId })
|
const { data: user, isLoading, error, refetch } = trpc.user.get.useQuery({ id: userId })
|
||||||
const updateUser = trpc.user.update.useMutation()
|
const updateUser = trpc.user.update.useMutation()
|
||||||
const sendInvitation = trpc.user.sendInvitation.useMutation()
|
const sendInvitation = trpc.user.sendInvitation.useMutation()
|
||||||
|
|
||||||
|
|
@ -121,14 +121,19 @@ export default function MemberDetailPage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (error || !user) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Alert variant="destructive">
|
<Alert variant="destructive">
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<AlertTitle>Member not found</AlertTitle>
|
<AlertTitle>Error Loading Member</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
The member you're looking for does not exist.
|
{error?.message || 'The member you\'re looking for does not exist.'}
|
||||||
|
{process.env.NODE_ENV === 'development' && (
|
||||||
|
<div className="mt-2 text-xs opacity-75">
|
||||||
|
User ID: {userId}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import {
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
import { AvatarUpload } from '@/components/shared/avatar-upload'
|
import { AvatarUpload } from '@/components/shared/avatar-upload'
|
||||||
import { UserAvatar } from '@/components/shared/user-avatar'
|
import { UserAvatar } from '@/components/shared/user-avatar'
|
||||||
|
import { TagInput } from '@/components/shared/tag-input'
|
||||||
import {
|
import {
|
||||||
Loader2,
|
Loader2,
|
||||||
Save,
|
Save,
|
||||||
|
|
@ -43,6 +44,7 @@ import {
|
||||||
Bell,
|
Bell,
|
||||||
Trash2,
|
Trash2,
|
||||||
User,
|
User,
|
||||||
|
Tags,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
export default function ProfileSettingsPage() {
|
export default function ProfileSettingsPage() {
|
||||||
|
|
@ -58,6 +60,7 @@ export default function ProfileSettingsPage() {
|
||||||
const [bio, setBio] = useState('')
|
const [bio, setBio] = useState('')
|
||||||
const [phoneNumber, setPhoneNumber] = useState('')
|
const [phoneNumber, setPhoneNumber] = useState('')
|
||||||
const [notificationPreference, setNotificationPreference] = useState('EMAIL')
|
const [notificationPreference, setNotificationPreference] = useState('EMAIL')
|
||||||
|
const [expertiseTags, setExpertiseTags] = useState<string[]>([])
|
||||||
const [profileLoaded, setProfileLoaded] = useState(false)
|
const [profileLoaded, setProfileLoaded] = useState(false)
|
||||||
|
|
||||||
// Password form state
|
// Password form state
|
||||||
|
|
@ -76,6 +79,7 @@ export default function ProfileSettingsPage() {
|
||||||
setBio((meta.bio as string) || '')
|
setBio((meta.bio as string) || '')
|
||||||
setPhoneNumber(user.phoneNumber || '')
|
setPhoneNumber(user.phoneNumber || '')
|
||||||
setNotificationPreference(user.notificationPreference || 'EMAIL')
|
setNotificationPreference(user.notificationPreference || 'EMAIL')
|
||||||
|
setExpertiseTags(user.expertiseTags || [])
|
||||||
setProfileLoaded(true)
|
setProfileLoaded(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,6 +90,7 @@ export default function ProfileSettingsPage() {
|
||||||
bio,
|
bio,
|
||||||
phoneNumber: phoneNumber || null,
|
phoneNumber: phoneNumber || null,
|
||||||
notificationPreference: notificationPreference as 'EMAIL' | 'WHATSAPP' | 'BOTH' | 'NONE',
|
notificationPreference: notificationPreference as 'EMAIL' | 'WHATSAPP' | 'BOTH' | 'NONE',
|
||||||
|
expertiseTags,
|
||||||
})
|
})
|
||||||
toast.success('Profile updated successfully')
|
toast.success('Profile updated successfully')
|
||||||
refetch()
|
refetch()
|
||||||
|
|
@ -294,6 +299,41 @@ export default function ProfileSettingsPage() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Expertise Tags */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Tags className="h-5 w-5" />
|
||||||
|
Expertise Tags
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Select your areas of expertise to help with project matching
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<TagInput
|
||||||
|
value={expertiseTags}
|
||||||
|
onChange={setExpertiseTags}
|
||||||
|
placeholder="Select your expertise areas..."
|
||||||
|
maxTags={15}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleSaveProfile}
|
||||||
|
disabled={updateProfile.isPending}
|
||||||
|
>
|
||||||
|
{updateProfile.isPending ? (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Save className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
Save Expertise
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Change Password */}
|
{/* Change Password */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -78,10 +78,11 @@ export const userRouter = router({
|
||||||
bio: z.string().max(1000).optional(),
|
bio: z.string().max(1000).optional(),
|
||||||
phoneNumber: z.string().max(20).optional().nullable(),
|
phoneNumber: z.string().max(20).optional().nullable(),
|
||||||
notificationPreference: z.enum(['EMAIL', 'WHATSAPP', 'BOTH', 'NONE']).optional(),
|
notificationPreference: z.enum(['EMAIL', 'WHATSAPP', 'BOTH', 'NONE']).optional(),
|
||||||
|
expertiseTags: z.array(z.string()).max(15).optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const { bio, ...directFields } = input
|
const { bio, expertiseTags, ...directFields } = input
|
||||||
|
|
||||||
// If bio is provided, merge it into metadataJson
|
// If bio is provided, merge it into metadataJson
|
||||||
let metadataJson: Prisma.InputJsonValue | undefined
|
let metadataJson: Prisma.InputJsonValue | undefined
|
||||||
|
|
@ -99,6 +100,7 @@ export const userRouter = router({
|
||||||
data: {
|
data: {
|
||||||
...directFields,
|
...directFields,
|
||||||
...(metadataJson !== undefined && { metadataJson }),
|
...(metadataJson !== undefined && { metadataJson }),
|
||||||
|
...(expertiseTags !== undefined && { expertiseTags }),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
@ -241,7 +243,9 @@ export const userRouter = router({
|
||||||
get: adminProcedure
|
get: adminProcedure
|
||||||
.input(z.object({ id: z.string() }))
|
.input(z.object({ id: z.string() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
return ctx.prisma.user.findUniqueOrThrow({
|
console.log('[user.get] Fetching user:', input.id)
|
||||||
|
try {
|
||||||
|
const user = await ctx.prisma.user.findUniqueOrThrow({
|
||||||
where: { id: input.id },
|
where: { id: input.id },
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
|
|
@ -249,6 +253,12 @@ export const userRouter = router({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
console.log('[user.get] Found user:', user.email)
|
||||||
|
return user
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[user.get] Error fetching user:', input.id, error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue