feat: add project languages

feat/languages
Arik Chakma 2 months ago
parent 447bf4eb0f
commit bfb3a3eb30
  1. 170
      src/components/Projects/ListProjectSolutions.tsx
  2. 11
      src/components/Projects/SubmitProjectModal.tsx

@ -15,6 +15,40 @@ import { VoteButton } from './VoteButton.tsx';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx'; import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import { cn } from '../../lib/classname.ts'; import { cn } from '../../lib/classname.ts';
const languageColors = new Map([
['JavaScript', 'bg-[#f1e05a]'],
['Python', 'bg-[#3572A5]'],
['Java', 'bg-[#b07219]'],
['HTML', 'bg-[#e34c26]'],
['CSS', 'bg-[#563d7c]'],
['C++', 'bg-[#f34b7d]'],
['C', 'bg-[#555555]'],
['Go', 'bg-[#00ADD8]'],
['TypeScript', 'bg-[#2b7489]'],
['Shell', 'bg-[#89e051]'],
['Ruby', 'bg-[#701516]'],
['PHP', 'bg-[#4F5D95]'],
['Rust', 'bg-[#dea584]'],
['Swift', 'bg-[#ffac45]'],
['Kotlin', 'bg-[#A97BFF]'],
['Dart', 'bg-[#00B4AB]'],
['Scala', 'bg-[#c22d40]'],
['Objective-C', 'bg-[#438eff]'],
['Vue', 'bg-[#41b883]'],
['R', 'bg-[#198CE7]'],
['Perl', 'bg-[#0298c3]'],
['Haskell', 'bg-[#5e5086]'],
['Lua', 'bg-[#000080]'],
['Matlab', 'bg-[#e16737]'],
['Vim script', 'bg-[#199f4b]'],
['Elixir', 'bg-[#6e4a7e]'],
['Erlang', 'bg-[#B83998]'],
['Clojure', 'bg-[#db5855]'],
['Markdown', 'bg-[#083fa1]'],
['TeX', 'bg-[#3D6117]'],
['SQL', 'bg-[#e38c00]'],
]);
export interface ProjectStatusDocument { export interface ProjectStatusDocument {
_id?: string; _id?: string;
@ -24,6 +58,7 @@ export interface ProjectStatusDocument {
startedAt?: Date; startedAt?: Date;
submittedAt?: Date; submittedAt?: Date;
repositoryUrl?: string; repositoryUrl?: string;
languages?: string[];
upvotes: number; upvotes: number;
downvotes: number; downvotes: number;
@ -234,73 +269,90 @@ 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 isVisited = alreadyVisitedSolutions[solution._id!];
const avatar = solution.user.avatar || ''; const avatar = solution.user.avatar || '';
const languages = solution?.languages || [];
return ( return (
<div <div
key={solution._id} key={solution._id}
className={ className="flex flex-col gap-2 py-2 text-sm text-gray-500"
'flex flex-col justify-between gap-2 py-2 text-sm text-gray-500 sm:flex-row sm:items-center sm:gap-0'
}
> >
<div className="flex items-center gap-1.5"> <div className="flex flex-col justify-between gap-2 text-sm text-gray-500 sm:flex-row sm:items-center sm:gap-0">
<img <div className="flex items-center gap-1.5">
src={ <img
avatar src={
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}` avatar
: '/images/default-avatar.png' ? `${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" 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 overflow-hidden rounded-full border">
<VoteButton
icon={ThumbsUp}
isActive={solution?.voteType === 'upvote'}
count={solution.upvotes || 0}
onClick={() => {
handleSubmitVote(solution._id!, 'upvote');
}}
/> />
<span className="font-medium text-black">
<VoteButton {solution.user.name}
icon={ThumbsDown} </span>
isActive={solution?.voteType === 'downvote'} <span className="hidden sm:inline">
count={solution.downvotes || 0} {submittedAlternatives[
hideCount={true} counter % submittedAlternatives.length
onClick={() => { ] || 'submitted their solution'}
handleSubmitVote(solution._id!, 'downvote'); </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 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>
<a
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={(e) => {
e.preventDefault();
setShowLeavingRoadmapModal(solution);
}} }}
/> target="_blank"
</span> href={solution.repositoryUrl}
>
<a <GitHubIcon className="h-4 w-4 text-current" />
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" Visit Solution
onClick={(e) => { </a>
e.preventDefault(); </div>
setShowLeavingRoadmapModal(solution); </div>
}}
target="_blank" <div className="flex justify-end gap-2.5">
href={solution.repositoryUrl} {languages.map((language) => (
> <span
<GitHubIcon className="h-4 w-4 text-current" /> key={language}
Visit Solution className="flex items-center gap-2 text-sm"
</a> >
<span
className={cn(
'h-2 w-2 rounded-full',
languageColors.get(language) || 'bg-gray-400',
)}
/>
{language}
</span>
))}
</div> </div>
</div> </div>
); );

@ -170,10 +170,19 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
projectUrlExists: 'success', projectUrlExists: 'success',
}); });
const languagesUrl = `${mainApiUrl}/languages`;
const languagesResponse = await fetch(languagesUrl);
let languages: string[] = [];
if (languagesResponse.ok) {
const languagesData = await languagesResponse.json();
languages = Object.keys(languagesData || {})?.slice(0, 4);
}
const submitProjectUrl = `${import.meta.env.PUBLIC_API_URL}/v1-submit-project/${projectId}`; const submitProjectUrl = `${import.meta.env.PUBLIC_API_URL}/v1-submit-project/${projectId}`;
const { response: submitResponse, error } = const { response: submitResponse, error } =
await httpPost<SubmitProjectResponse>(submitProjectUrl, { await httpPost<SubmitProjectResponse>(submitProjectUrl, {
repositoryUrl: repoUrl, repositoryUrl: repoUrl,
languages,
}); });
if (error || !submitResponse) { if (error || !submitResponse) {
@ -272,7 +281,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
<button <button
type="submit" type="submit"
className="mt-2 w-full rounded-lg bg-black p-2 font-medium text-white disabled:opacity-50 text-sm" className="mt-2 w-full rounded-lg bg-black p-2 text-sm font-medium text-white disabled:opacity-50"
disabled={isLoading} disabled={isLoading}
> >
{isLoading ? 'Verifying...' : 'Verify and Submit'} {isLoading ? 'Verifying...' : 'Verify and Submit'}

Loading…
Cancel
Save