Improve UI for project solutions

pull/8172/head
Kamran Ahmed 1 week ago
parent 314eb5d7d2
commit 5cc4b834d1
  1. 12
      src/components/Projects/ListProjectSolutions.tsx
  2. 126
      src/components/Projects/ProjectSolutionRow.tsx

@ -213,6 +213,13 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
const selectedLanguage = pageState.language; const selectedLanguage = pageState.language;
const setSelectedLanguage = (language: string) => {
setPageState((prev) => ({
...prev,
language: prev.language === language ? '' : language,
}));
};
return ( return (
<div className="mb-4 overflow-hidden rounded-lg border bg-white p-3 sm:p-5"> <div className="mb-4 overflow-hidden rounded-lg border bg-white p-3 sm:p-5">
{leavingRoadmapModal} {leavingRoadmapModal}
@ -221,7 +228,9 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
<h1 className="mb-1 text-xl font-semibold"> <h1 className="mb-1 text-xl font-semibold">
{projectData.title} Solutions {projectData.title} Solutions
</h1> </h1>
<p className="text-sm text-gray-500">{projectData.description}</p> <p className="text-sm text-gray-500">
Solutions submitted by the community
</p>
</div> </div>
{!isLoading && ( {!isLoading && (
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
@ -260,6 +269,7 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
counter={counter} counter={counter}
onVote={handleSubmitVote} onVote={handleSubmitVote}
onVisitSolution={setShowLeavingRoadmapModal} onVisitSolution={setShowLeavingRoadmapModal}
onLanguageClick={setSelectedLanguage}
/> />
))} ))}
</div> </div>

@ -43,69 +43,95 @@ type ProjectSolutionRowProps = {
counter: number; counter: number;
onVote: (solutionId: string, voteType: AllowedVoteType) => void; onVote: (solutionId: string, voteType: AllowedVoteType) => void;
onVisitSolution: (solution: ProjectSolutionRowProps['solution']) => void; onVisitSolution: (solution: ProjectSolutionRowProps['solution']) => void;
onLanguageClick?: (language: string) => void;
}; };
export function ProjectSolutionRow(props: ProjectSolutionRowProps) { export function ProjectSolutionRow(props: ProjectSolutionRowProps) {
const { solution, counter, onVote, onVisitSolution } = props; const { solution, counter, onVote, onVisitSolution, onLanguageClick } = props;
const avatar = solution.user.avatar || ''; const avatar = solution.user.avatar || '';
return ( return (
<div className="flex flex-col gap-2 py-2 text-sm text-gray-500"> <div className="group flex flex-col border-gray-100 px-3 py-2.5 text-sm hover:bg-gray-50/50 sm:flex-row sm:justify-between">
<div className="flex flex-col justify-between gap-2 text-sm text-gray-500 sm:flex-row sm:items-center sm:gap-0"> <div className="flex min-w-0 items-start gap-3">
<div className="flex items-center gap-1.5"> <img
<img src={
src={ avatar
avatar ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}` : '/images/default-avatar.png'
: '/images/default-avatar.png' }
} alt={solution.user.name}
alt={solution.user.name} className="h-7 w-7 flex-shrink-0 rounded-full sm:h-8 sm:w-8"
className="mr-0.5 h-7 w-7 rounded-full" />
/> <div className="min-w-0 flex-auto">
<span className="font-medium text-black">{solution.user.name}</span> <div className="flex flex-wrap items-baseline gap-x-1.5 gap-y-0.5">
<span className="hidden sm:inline"> <span className="max-w-[150px] truncate font-medium text-gray-900 sm:max-w-[180px]">
{submittedAlternatives[counter % submittedAlternatives.length] || {solution.user.name}
'submitted their solution'} </span>
</span>{' '} <span className="hidden truncate text-xs text-gray-500 sm:block sm:text-sm">
<span className="flex-grow text-right text-gray-400 sm:flex-grow-0 sm:text-left sm:font-medium sm:text-black"> {submittedAlternatives[counter % submittedAlternatives.length] ||
{getRelativeTimeString(solution?.submittedAt!)} 'submitted their solution'}
</span> </span>
</div> <span
className="text-xs text-gray-400"
title={new Date(solution?.submittedAt!).toLocaleString()}
>
· {getRelativeTimeString(solution?.submittedAt!)}
</span>
</div>
<div className="flex items-center justify-end gap-1"> <div className="mt-2.5 flex gap-1.5">
<span className="flex shrink-0 overflow-hidden rounded-full border"> <div className="flex gap-1">
<VoteButton <span className="flex shrink-0 overflow-hidden rounded-full border">
icon={ThumbsUp} <VoteButton
isActive={solution?.voteType === 'upvote'} icon={ThumbsUp}
count={solution.upvotes || 0} isActive={solution?.voteType === 'upvote'}
onClick={() => { count={solution.upvotes || 0}
onVote(solution._id!, 'upvote'); onClick={() => {
}} onVote(solution._id!, 'upvote');
/> }}
/>
<VoteButton
icon={ThumbsDown}
isActive={solution?.voteType === 'downvote'}
count={solution.downvotes || 0}
hideCount={true}
onClick={() => {
onVote(solution._id!, 'downvote');
}}
/>
</span>
</div>
<VoteButton <button
icon={ThumbsDown} className="flex items-center gap-1 rounded-full border px-2 py-1 text-xs text-black transition-colors hover:border-black hover:bg-black hover:text-white"
isActive={solution?.voteType === 'downvote'}
count={solution.downvotes || 0}
hideCount={true}
onClick={() => { onClick={() => {
onVote(solution._id!, 'downvote'); onVisitSolution(solution);
}} }}
/> >
</span> <GitHubIcon className="h-3.5 w-3.5 text-current" />
<span>Visit Solution</span>
<button </button>
className="ml-1 flex items-center gap-1 rounded-full border px-2 py-1 text-xs text-black transition-colors hover:border-black hover:bg-black hover:text-white" </div>
onClick={() => {
onVisitSolution(solution);
}}
>
<GitHubIcon className="h-4 w-4 text-current" />
Visit Solution
</button>
</div> </div>
</div> </div>
<div className="mt-2.5 hidden sm:mt-0 sm:block sm:pl-4">
{solution.languages && solution.languages.length > 0 && (
<div className="flex flex-wrap items-center gap-1.5">
{solution.languages.slice(0, 2).map((lang) => (
<button
key={lang}
onClick={() => onLanguageClick?.(lang)}
className="inline-flex items-center rounded-md border border-gray-200 bg-white px-2 py-0.5 text-xs font-medium text-gray-700 transition-colors hover:border-gray-300 hover:bg-gray-50 hover:text-gray-900"
>
{lang}
</button>
))}
</div>
)}
</div>
</div> </div>
); );
} }

Loading…
Cancel
Save