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 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 sendInvitation = trpc.user.sendInvitation.useMutation()
|
||||
|
||||
|
|
@ -121,14 +121,19 @@ export default function MemberDetailPage() {
|
|||
)
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
if (error || !user) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Member not found</AlertTitle>
|
||||
<AlertTitle>Error Loading Member</AlertTitle>
|
||||
<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>
|
||||
</Alert>
|
||||
<Button asChild>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import {
|
|||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { AvatarUpload } from '@/components/shared/avatar-upload'
|
||||
import { UserAvatar } from '@/components/shared/user-avatar'
|
||||
import { TagInput } from '@/components/shared/tag-input'
|
||||
import {
|
||||
Loader2,
|
||||
Save,
|
||||
|
|
@ -43,6 +44,7 @@ import {
|
|||
Bell,
|
||||
Trash2,
|
||||
User,
|
||||
Tags,
|
||||
} from 'lucide-react'
|
||||
|
||||
export default function ProfileSettingsPage() {
|
||||
|
|
@ -58,6 +60,7 @@ export default function ProfileSettingsPage() {
|
|||
const [bio, setBio] = useState('')
|
||||
const [phoneNumber, setPhoneNumber] = useState('')
|
||||
const [notificationPreference, setNotificationPreference] = useState('EMAIL')
|
||||
const [expertiseTags, setExpertiseTags] = useState<string[]>([])
|
||||
const [profileLoaded, setProfileLoaded] = useState(false)
|
||||
|
||||
// Password form state
|
||||
|
|
@ -76,6 +79,7 @@ export default function ProfileSettingsPage() {
|
|||
setBio((meta.bio as string) || '')
|
||||
setPhoneNumber(user.phoneNumber || '')
|
||||
setNotificationPreference(user.notificationPreference || 'EMAIL')
|
||||
setExpertiseTags(user.expertiseTags || [])
|
||||
setProfileLoaded(true)
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +90,7 @@ export default function ProfileSettingsPage() {
|
|||
bio,
|
||||
phoneNumber: phoneNumber || null,
|
||||
notificationPreference: notificationPreference as 'EMAIL' | 'WHATSAPP' | 'BOTH' | 'NONE',
|
||||
expertiseTags,
|
||||
})
|
||||
toast.success('Profile updated successfully')
|
||||
refetch()
|
||||
|
|
@ -294,6 +299,41 @@ export default function ProfileSettingsPage() {
|
|||
</CardContent>
|
||||
</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 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
|
|
|||
|
|
@ -78,10 +78,11 @@ export const userRouter = router({
|
|||
bio: z.string().max(1000).optional(),
|
||||
phoneNumber: z.string().max(20).optional().nullable(),
|
||||
notificationPreference: z.enum(['EMAIL', 'WHATSAPP', 'BOTH', 'NONE']).optional(),
|
||||
expertiseTags: z.array(z.string()).max(15).optional(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { bio, ...directFields } = input
|
||||
const { bio, expertiseTags, ...directFields } = input
|
||||
|
||||
// If bio is provided, merge it into metadataJson
|
||||
let metadataJson: Prisma.InputJsonValue | undefined
|
||||
|
|
@ -99,6 +100,7 @@ export const userRouter = router({
|
|||
data: {
|
||||
...directFields,
|
||||
...(metadataJson !== undefined && { metadataJson }),
|
||||
...(expertiseTags !== undefined && { expertiseTags }),
|
||||
},
|
||||
})
|
||||
}),
|
||||
|
|
@ -241,14 +243,22 @@ export const userRouter = router({
|
|||
get: adminProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
return ctx.prisma.user.findUniqueOrThrow({
|
||||
where: { id: input.id },
|
||||
include: {
|
||||
_count: {
|
||||
select: { assignments: true, mentorAssignments: true },
|
||||
console.log('[user.get] Fetching user:', input.id)
|
||||
try {
|
||||
const user = await ctx.prisma.user.findUniqueOrThrow({
|
||||
where: { id: input.id },
|
||||
include: {
|
||||
_count: {
|
||||
select: { assignments: true, mentorAssignments: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
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