Skip to main content
The profiles router handles user profile management, including bio, skills, work availability, and reputation/rating systems.

Queries

getProfile

Get a user’s public profile by user ID or handle. Access: Public (respects privacy settings)
userId
string
User UUID
handle
string
User handle (3-20 characters)
Either userId or handle must be provided.
Response:
{
  success: boolean;
  data: {
    user: {
      id: string;
      name: string | null;
      email: string; // Empty if profile is private
      image: string | null;
      handle: string | null;
      isProfilePrivate: boolean;
      createdAt: Date;
    };
    profile: UserProfile | null; // null if private
    reputation: UserReputation | null; // null if private
  };
  isPrivate: boolean;
}
Example:
// Get by handle
const { data } = await trpc.profiles.getProfile.query({
  handle: "johndoe"
});

// Get by user ID
const { data } = await trpc.profiles.getProfile.query({
  userId: "uuid-here"
});

if (data?.isPrivate) {
  console.log("This profile is private");
} else {
  console.log(`Bio: ${data?.data.profile?.bio}`);
}

getMyProfile

Get the current user’s own profile. Access: Protected Response:
{
  success: boolean;
  data: {
    user: {
      id: string;
      name: string | null;
      email: string;
      image: string | null;
      createdAt: Date;
    };
    profile: UserProfile | null;
    reputation: UserReputation | null;
  };
}

getTopContributors

Get top contributors ranked by earnings, bounties completed, or rating. Access: Public
limit
number
default:"10"
Number of contributors to return (1-50)
sortBy
string
default:"totalEarned"
Sort criteria: totalEarned, bountiesCompleted, or averageRating
Example:
const { data } = await trpc.profiles.getTopContributors.query({
  limit: 10,
  sortBy: 'bountiesCompleted'
});

data?.data.forEach(contributor => {
  console.log(`${contributor.user.name}: ${contributor.reputation.bountiesCompleted} bounties`);
});

getUserRatings

Get ratings/reviews for a specific user. Access: Public
userId
string
required
User UUID
page
number
default:"1"
Page number
limit
number
default:"10"
Results per page (1-50)
Response:
{
  success: boolean;
  data: Array<{
    rating: {
      id: string;
      rating: number; // 1-5
      comment: string | null;
      createdAt: Date;
    };
    rater: {
      id: string;
      name: string | null;
      image: string | null;
    };
  }>;
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  };
}

searchProfiles

Search user profiles by name, bio, or skills. Access: Public
query
string
required
Search term (minimum 1 character)
skills
string[]
Filter by specific skills
availableForWork
boolean
Filter by work availability
page
number
default:"1"
Page number
limit
number
default:"20"
Results per page (1-50)
Example:
const { data } = await trpc.profiles.searchProfiles.query({
  query: "react developer",
  skills: ["React", "TypeScript"],
  availableForWork: true,
  limit: 20
});

Mutations

updateProfile

Update the current user’s profile. Access: Protected
bio
string
User bio (max 500 characters)
location
string
Location (max 100 characters)
website
string
Website URL
githubUsername
string
GitHub username (max 50 characters)
twitterUsername
string
Twitter/X username (max 50 characters)
linkedinUrl
string
LinkedIn profile URL
skills
string[]
Array of skill tags
preferredLanguages
string[]
Preferred programming languages
hourlyRate
string
Hourly rate (must match pattern: /^\d+(\.\d{1,2})?$/)
currency
string
default:"USD"
Currency for hourly rate
timezone
string
Timezone identifier
availableForWork
boolean
Whether user is available for work
Example:
const result = await trpc.profiles.updateProfile.mutate({
  bio: "Full-stack developer specializing in React and Node.js",
  location: "San Francisco, CA",
  website: "https://johndoe.dev",
  githubUsername: "johndoe",
  skills: ["React", "TypeScript", "Node.js", "PostgreSQL"],
  preferredLanguages: ["JavaScript", "TypeScript", "Python"],
  hourlyRate: "75.00",
  currency: "USD",
  availableForWork: true
});

console.log(result.message); // "Profile updated successfully"

rateUser

Rate/review a user after completing a bounty together. Access: Protected
ratedUserId
string
required
UUID of user to rate
bountyId
string
required
UUID of the bounty context
rating
number
required
Rating value (1-5)
comment
string
Optional review comment (max 500 characters)
Restrictions:
  • Cannot rate yourself
  • Can only rate once per user per bounty
Example:
try {
  const result = await trpc.profiles.rateUser.mutate({
    ratedUserId: "user-uuid",
    bountyId: "bounty-uuid",
    rating: 5,
    comment: "Great work! Delivered on time and exceeded expectations."
  });
  
  console.log(result.message); // "Rating submitted successfully"
} catch (error) {
  if (error.message.includes('already rated')) {
    console.error('You already rated this user for this bounty');
  }
}

Code Examples

Display User Profile

import { trpc } from '@/lib/trpc';

const UserProfile = ({ handle }: { handle: string }) => {
  const { data, isLoading } = trpc.profiles.getProfile.useQuery({ handle });

  if (isLoading) return <div>Loading...</div>;
  if (data?.isPrivate) return <div>This profile is private</div>;

  const { user, profile, reputation } = data?.data || {};

  return (
    <div className="profile">
      <img src={user?.image || '/default-avatar.png'} alt={user?.name || 'User'} />
      <h1>{user?.name}</h1>
      <p>@{user?.handle}</p>
      
      {profile && (
        <div>
          <p>{profile.bio}</p>
          <p>Location: {profile.location}</p>
          <p>Skills: {profile.skills?.join(', ')}</p>
          {profile.availableForWork && (
            <span className="badge">Available for work</span>
          )}
        </div>
      )}
      
      {reputation && (
        <div className="stats">
          <div>
            <strong>{reputation.bountiesCompleted}</strong>
            <span>Bounties Completed</span>
          </div>
          <div>
            <strong>${reputation.totalEarned}</strong>
            <span>Total Earned</span>
          </div>
          <div>
            <strong>{reputation.averageRating}/5</strong>
            <span>Average Rating</span>
          </div>
        </div>
      )}
    </div>
  );
};

Profile Editor Form

import { trpc } from '@/lib/trpc';
import { useState } from 'react';

const ProfileEditor = () => {
  const { data: profile } = trpc.profiles.getMyProfile.useQuery();
  const updateMutation = trpc.profiles.updateProfile.useMutation();
  
  const [formData, setFormData] = useState({
    bio: profile?.data.profile?.bio || '',
    location: profile?.data.profile?.location || '',
    skills: profile?.data.profile?.skills || [],
    availableForWork: profile?.data.profile?.availableForWork || false
  });

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    try {
      await updateMutation.mutateAsync(formData);
      alert('Profile updated!');
    } catch (error) {
      alert(`Error: ${error.message}`);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        value={formData.bio}
        onChange={(e) => setFormData({ ...formData, bio: e.target.value })}
        placeholder="Tell us about yourself..."
        maxLength={500}
      />
      
      <input
        type="text"
        value={formData.location}
        onChange={(e) => setFormData({ ...formData, location: e.target.value })}
        placeholder="Location"
        maxLength={100}
      />
      
      <label>
        <input
          type="checkbox"
          checked={formData.availableForWork}
          onChange={(e) => setFormData({ ...formData, availableForWork: e.target.checked })}
        />
        Available for work
      </label>
      
      <button type="submit" disabled={updateMutation.isLoading}>
        {updateMutation.isLoading ? 'Saving...' : 'Save Profile'}
      </button>
    </form>
  );
};

Top Contributors Leaderboard

import { trpc } from '@/lib/trpc';

const Leaderboard = () => {
  const { data } = trpc.profiles.getTopContributors.useQuery({
    limit: 10,
    sortBy: 'totalEarned'
  });

  return (
    <div className="leaderboard">
      <h2>Top Contributors</h2>
      <ol>
        {data?.data.map((contributor, index) => (
          <li key={contributor.user.id}>
            <span className="rank">#{index + 1}</span>
            <img src={contributor.user.image || '/default-avatar.png'} />
            <div>
              <strong>{contributor.user.name}</strong>
              <div className="stats">
                <span>${contributor.reputation?.totalEarned} earned</span>
                <span>{contributor.reputation?.bountiesCompleted} bounties</span>
                <span>{contributor.reputation?.averageRating}/5 rating</span>
              </div>
            </div>
          </li>
        ))}
      </ol>
    </div>
  );
};

Rate a User

import { trpc } from '@/lib/trpc';
import { useState } from 'react';

const RateUserForm = ({ userId, bountyId }: { userId: string; bountyId: string }) => {
  const [rating, setRating] = useState(5);
  const [comment, setComment] = useState('');
  const rateMutation = trpc.profiles.rateUser.useMutation();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    try {
      await rateMutation.mutateAsync({
        ratedUserId: userId,
        bountyId,
        rating,
        comment
      });
      
      alert('Rating submitted!');
    } catch (error) {
      alert(error.message);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div className="star-rating">
        {[1, 2, 3, 4, 5].map(star => (
          <button
            key={star}
            type="button"
            onClick={() => setRating(star)}
            className={star <= rating ? 'active' : ''}
          >

          </button>
        ))}
      </div>
      
      <textarea
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        placeholder="Write a review (optional)"
        maxLength={500}
      />
      
      <button type="submit" disabled={rateMutation.isLoading}>
        Submit Rating
      </button>
    </form>
  );
};

Build docs developers (and LLMs) love