Refactor project solutoin row

pull/8172/head
Kamran Ahmed 1 week ago
parent ad2597f610
commit 314eb5d7d2
  1. 97
      src/components/Projects/ListProjectSolutions.tsx
  2. 31
      src/components/Projects/ProjectSolutionModal.tsx
  3. 111
      src/components/Projects/ProjectSolutionRow.tsx

@ -17,6 +17,7 @@ import { SelectLanguages } from './SelectLanguages.tsx';
import type { ProjectFrontmatter } from '../../lib/project.ts'; import type { ProjectFrontmatter } from '../../lib/project.ts';
import { ProjectSolutionModal } from './ProjectSolutionModal.tsx'; import { ProjectSolutionModal } from './ProjectSolutionModal.tsx';
import { SortProjects } from './SortProjects.tsx'; import { SortProjects } from './SortProjects.tsx';
import { ProjectSolutionRow } from './ProjectSolutionRow';
export interface ProjectStatusDocument { export interface ProjectStatusDocument {
_id?: string; _id?: string;
@ -72,30 +73,6 @@ type ListProjectSolutionsProps = {
projectId: string; projectId: string;
}; };
export const submittedAlternatives = [
'submitted their solution',
'got it done',
'submitted their take',
'finished the project',
'submitted their work',
'completed the project',
'got it done',
'delivered their project',
'handed in their solution',
'provided their deliverables',
'submitted their approach',
'sent in their project',
'presented their take',
'shared their completed task',
'submitted their approach',
'completed it',
'finalized their solution',
'delivered their approach',
'turned in their project',
'submitted their final draft',
'delivered their solution',
];
export function ListProjectSolutions(props: ListProjectSolutionsProps) { export function ListProjectSolutions(props: ListProjectSolutionsProps) {
const { projectId, project: projectData } = props; const { projectId, project: projectData } = props;
@ -276,73 +253,15 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
) : ( ) : (
<> <>
<div className="flex min-h-[500px] flex-col divide-y divide-gray-100"> <div className="flex min-h-[500px] flex-col divide-y divide-gray-100">
{solutions?.data.map((solution, counter) => { {solutions?.data.map((solution, counter) => (
const avatar = solution.user.avatar || ''; <ProjectSolutionRow
return (
<div
key={solution._id} key={solution._id}
className="flex flex-col gap-2 py-2 text-sm text-gray-500" solution={solution}
> counter={counter}
<div className="flex flex-col justify-between gap-2 text-sm text-gray-500 sm:flex-row sm:items-center sm:gap-0"> onVote={handleSubmitVote}
<div className="flex items-center gap-1.5"> onVisitSolution={setShowLeavingRoadmapModal}
<img
src={
avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png'
}
alt={solution.user.name}
className="mr-0.5 h-7 w-7 rounded-full"
/>
<span className="font-medium text-black">
{solution.user.name}
</span>
<span className="hidden sm:inline">
{submittedAlternatives[
counter % submittedAlternatives.length
] || 'submitted their solution'}
</span>{' '}
<span className="flex-grow text-right text-gray-400 sm:flex-grow-0 sm:text-left sm:font-medium sm:text-black">
{getRelativeTimeString(solution?.submittedAt!)}
</span>
</div>
<div className="flex items-center justify-end gap-1">
<span className="flex shrink-0 overflow-hidden rounded-full border">
<VoteButton
icon={ThumbsUp}
isActive={solution?.voteType === 'upvote'}
count={solution.upvotes || 0}
onClick={() => {
handleSubmitVote(solution._id!, 'upvote');
}}
/>
<VoteButton
icon={ThumbsDown}
isActive={solution?.voteType === 'downvote'}
count={solution.downvotes || 0}
hideCount={true}
onClick={() => {
handleSubmitVote(solution._id!, 'downvote');
}}
/> />
</span> ))}
<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"
onClick={() => {
setShowLeavingRoadmapModal(solution);
}}
>
<GitHubIcon className="h-4 w-4 text-current" />
Visit Solution
</button>
</div>
</div>
</div>
);
})}
</div> </div>
{(solutions?.totalPages || 0) > 1 && ( {(solutions?.totalPages || 0) > 1 && (

@ -1,20 +1,17 @@
import { ArrowUpRight, ThumbsDown, ThumbsUp } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useToast } from '../../hooks/use-toast';
import { deleteUrlParam, getUrlParams } from '../../lib/browser'; import { deleteUrlParam, getUrlParams } from '../../lib/browser';
import { ModalLoader } from '../UserProgress/ModalLoader';
import { Modal } from '../Modal';
import { httpGet, httpPost } from '../../lib/http';
import {
submittedAlternatives,
type AllowedVoteType,
} from './ListProjectSolutions';
import { getRelativeTimeString } from '../../lib/date'; import { getRelativeTimeString } from '../../lib/date';
import { ArrowUpRight, ThumbsDown, ThumbsUp, Trophy } from 'lucide-react'; import { httpGet, httpPost } from '../../lib/http';
import { VoteButton } from './VoteButton';
import { GitHubIcon } from '../ReactIcons/GitHubIcon';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { pageProgressMessage } from '../../stores/page'; import { pageProgressMessage } from '../../stores/page';
import { useToast } from '../../hooks/use-toast'; import { Modal } from '../Modal';
import { GitHubIcon } from '../ReactIcons/GitHubIcon';
import { ModalLoader } from '../UserProgress/ModalLoader';
import { type AllowedVoteType } from './ListProjectSolutions';
import { VoteButton } from './VoteButton';
type UserProjectSolutionResponse = { type UserProjectSolutionResponse = {
id?: string; id?: string;
@ -135,8 +132,12 @@ export function ProjectSolutionModal(props: ProjectSolutionModalProps) {
bodyClassName={'h-auto'} bodyClassName={'h-auto'}
> >
<div className="relative p-6"> <div className="relative p-6">
<h1 className="text-2xl text-balance mb-1 font-bold text-gray-900">{projectTitle}</h1> <h1 className="mb-1 text-balance text-2xl font-bold text-gray-900">
<p className="text-sm text-balance text-gray-600">{projectDescription}</p> {projectTitle}
</h1>
<p className="text-balance text-sm text-gray-600">
{projectDescription}
</p>
<div className="my-5 rounded-lg bg-gray-100 p-4"> <div className="my-5 rounded-lg bg-gray-100 p-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@ -150,7 +151,9 @@ export function ProjectSolutionModal(props: ProjectSolutionModalProps) {
className="h-12 w-12 rounded-full border-2 border-white shadow-md" className="h-12 w-12 rounded-full border-2 border-white shadow-md"
/> />
<div> <div>
<h2 className="text-lg font-semibold text-gray-900">{solution?.user.name}'s Solution</h2> <h2 className="text-lg font-semibold text-gray-900">
{solution?.user.name}'s Solution
</h2>
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
Submitted their solution{' '} Submitted their solution{' '}
{getRelativeTimeString(solution?.submittedAt!)} {getRelativeTimeString(solution?.submittedAt!)}

@ -0,0 +1,111 @@
import { ThumbsDown, ThumbsUp } from 'lucide-react';
import { getRelativeTimeString } from '../../lib/date';
import { VoteButton } from './VoteButton';
import { GitHubIcon } from '../ReactIcons/GitHubIcon';
import type {
AllowedVoteType,
ProjectStatusDocument,
} from './ListProjectSolutions';
export const submittedAlternatives = [
'submitted their solution',
'got it done',
'submitted their take',
'finished the project',
'submitted their work',
'completed the project',
'got it done',
'delivered their project',
'handed in their solution',
'provided their deliverables',
'submitted their approach',
'sent in their project',
'presented their take',
'shared their completed task',
'submitted their approach',
'completed it',
'finalized their solution',
'delivered their approach',
'turned in their project',
'submitted their final draft',
'delivered their solution',
];
type ProjectSolutionRowProps = {
solution: ProjectStatusDocument & {
user: {
id: string;
name: string;
avatar: string;
};
voteType?: AllowedVoteType | 'none';
};
counter: number;
onVote: (solutionId: string, voteType: AllowedVoteType) => void;
onVisitSolution: (solution: ProjectSolutionRowProps['solution']) => void;
};
export function ProjectSolutionRow(props: ProjectSolutionRowProps) {
const { solution, counter, onVote, onVisitSolution } = props;
const avatar = solution.user.avatar || '';
return (
<div className="flex flex-col gap-2 py-2 text-sm text-gray-500">
<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 items-center gap-1.5">
<img
src={
avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png'
}
alt={solution.user.name}
className="mr-0.5 h-7 w-7 rounded-full"
/>
<span className="font-medium text-black">{solution.user.name}</span>
<span className="hidden sm:inline">
{submittedAlternatives[counter % submittedAlternatives.length] ||
'submitted their solution'}
</span>{' '}
<span className="flex-grow text-right text-gray-400 sm:flex-grow-0 sm:text-left sm:font-medium sm:text-black">
{getRelativeTimeString(solution?.submittedAt!)}
</span>
</div>
<div className="flex items-center justify-end gap-1">
<span className="flex shrink-0 overflow-hidden rounded-full border">
<VoteButton
icon={ThumbsUp}
isActive={solution?.voteType === 'upvote'}
count={solution.upvotes || 0}
onClick={() => {
onVote(solution._id!, 'upvote');
}}
/>
<VoteButton
icon={ThumbsDown}
isActive={solution?.voteType === 'downvote'}
count={solution.downvotes || 0}
hideCount={true}
onClick={() => {
onVote(solution._id!, 'downvote');
}}
/>
</span>
<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"
onClick={() => {
onVisitSolution(solution);
}}
>
<GitHubIcon className="h-4 w-4 text-current" />
Visit Solution
</button>
</div>
</div>
</div>
);
}
Loading…
Cancel
Save