Make project pages responsive

pull/6513/head
Kamran Ahmed 3 months ago
parent 377c86d8ad
commit 364c138fac
  1. 26
      src/components/Projects/ListProjectSolutions.tsx
  2. 81
      src/components/Projects/ProjectTabs.tsx
  3. 2
      src/components/Projects/VoteButton.tsx
  4. 6
      src/pages/projects/[projectId]/solutions.astro

@ -3,22 +3,14 @@ import { useToast } from '../../hooks/use-toast';
import { httpGet, httpPost } from '../../lib/http'; import { httpGet, httpPost } from '../../lib/http';
import { LoadingSolutions } from './LoadingSolutions'; import { LoadingSolutions } from './LoadingSolutions';
import { EmptySolutions } from './EmptySolutions'; import { EmptySolutions } from './EmptySolutions';
import { import { ThumbsDown, ThumbsUp } from 'lucide-react';
ArrowDown,
ArrowUp,
CalendarCheck,
ThumbsDown,
ThumbsUp,
} from 'lucide-react';
import { getRelativeTimeString } from '../../lib/date'; import { getRelativeTimeString } from '../../lib/date';
import { Pagination } from '../Pagination/Pagination'; import { Pagination } from '../Pagination/Pagination';
import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser'; import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser';
import { pageProgressMessage } from '../../stores/page'; import { pageProgressMessage } from '../../stores/page';
import { cn } from '../../lib/classname';
import { LeavingRoadmapWarningModal } from './LeavingRoadmapWarningModal'; import { LeavingRoadmapWarningModal } from './LeavingRoadmapWarningModal';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { CheckIcon } from '../ReactIcons/CheckIcon.tsx';
import { VoteButton } from './VoteButton.tsx'; import { VoteButton } from './VoteButton.tsx';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx'; import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
@ -239,7 +231,7 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
<section> <section>
{leavingRoadmapModal} {leavingRoadmapModal}
<div className="flex flex-col divide-y divide-gray-100 min-h-[500px]"> <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 isVisited = alreadyVisitedSolutions[solution._id!];
const avatar = solution.user.avatar || ''; const avatar = solution.user.avatar || '';
@ -247,7 +239,7 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
return ( return (
<div <div
key={solution._id} key={solution._id}
className="group flex items-center justify-between py-2 text-sm text-gray-500" className="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 items-center gap-1.5">
<img <img
@ -262,15 +254,17 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
<span className="font-medium text-black"> <span className="font-medium text-black">
{solution.user.name} {solution.user.name}
</span> </span>
{submittedAlternatives[ <span className="hidden sm:inline">
counter % submittedAlternatives.length {submittedAlternatives[
] || 'submitted their solution'}{' '} counter % submittedAlternatives.length
<span className="font-medium text-black"> ] || 'submitted their solution'}
</span>{' '}
<span className="text-gray-400 text-right sm:text-left flex-grow sm:flex-grow-0 sm:font-medium sm:text-black">
{getRelativeTimeString(solution?.submittedAt!)} {getRelativeTimeString(solution?.submittedAt!)}
</span> </span>
</div> </div>
<div className="5 flex items-center gap-1"> <div className="flex items-center justify-center sm:justify-end gap-1">
<span className="flex items-center overflow-hidden rounded-full border"> <span className="flex items-center overflow-hidden rounded-full border">
<VoteButton <VoteButton
icon={ThumbsUp} icon={ThumbsUp}

@ -1,9 +1,45 @@
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { Blocks, BoxSelect, StickyNote, Text } from 'lucide-react'; import {
Blocks,
BoxSelect,
type LucideIcon,
StickyNote,
Text,
} from 'lucide-react';
export const allowedProjectTabs = ['details', 'solutions'] as const; export const allowedProjectTabs = ['details', 'solutions'] as const;
export type AllowedProjectTab = (typeof allowedProjectTabs)[number]; export type AllowedProjectTab = (typeof allowedProjectTabs)[number];
type TabButtonProps = {
text: string;
icon: LucideIcon;
smText?: string;
isActive?: boolean;
href: string;
};
function TabButton(props: TabButtonProps) {
const { text, icon: ButtonIcon, smText, isActive, href } = props;
return (
<a
href={href}
className={cn('relative flex items-center gap-1 p-2', {
'text-black': isActive,
'opacity-40 hover:opacity-90': !isActive,
})}
>
{ButtonIcon && <ButtonIcon className="mr-1 inline-block h-4 w-4" />}
<span className="hidden sm:inline">{text}</span>
{smText && <span className="sm:hidden">{smText}</span>}
{isActive && (
<span className="absolute bottom-0 left-0 right-0 h-0.5 translate-y-1/2 bg-black rounded-t-md"></span>
)}
</a>
);
}
type ProjectTabsProps = { type ProjectTabsProps = {
activeTab: AllowedProjectTab; activeTab: AllowedProjectTab;
projectId: string; projectId: string;
@ -12,37 +48,22 @@ type ProjectTabsProps = {
export function ProjectTabs(props: ProjectTabsProps) { export function ProjectTabs(props: ProjectTabsProps) {
const { activeTab, projectId } = props; const { activeTab, projectId } = props;
const tabs = [
{ name: 'Project Details', value: 'details', icon: Text },
{ name: 'Community Solutions', value: 'solutions', icon: Blocks },
];
return ( return (
<div className="my-3 flex flex-row flex-wrap items-center gap-1.5 rounded-md border bg-white px-2.5 text-sm"> <div className="my-3 flex flex-row flex-wrap items-center gap-1.5 rounded-md border bg-white px-2.5 text-sm">
{tabs.map((tab) => { <TabButton
const isActive = tab.value === activeTab; text={'Project Detail'}
icon={Text}
return ( smText={'Details'}
<a isActive={activeTab === 'details'}
key={tab.value} href={`/projects/${projectId}`}
href={ />
tab.value === 'details' <TabButton
? `/projects/${projectId}` text={'Community Solutions'}
: `/projects/${projectId}/${tab.value}` icon={Blocks}
} smText={'Solutions'}
className={cn('relative flex items-center gap-1 p-2', { isActive={activeTab === 'solutions'}
'text-black': isActive, href={`/projects/${projectId}/solutions`}
'opacity-40 hover:opacity-90': !isActive, />
})}
>
{tab.icon && <tab.icon className="mr-1 inline-block h-4 w-4" />}
{tab.name}
{isActive && (
<span className="absolute bottom-0 left-0 right-0 h-0.5 translate-y-1/2 bg-black"></span>
)}
</a>
);
})}
</div> </div>
); );
} }

@ -12,7 +12,7 @@ export function VoteButton(props: VoteButtonProps) {
return ( return (
<button <button
className={cn( className={cn(
'flex items-center gap-1 px-2 py-1 text-sm text-gray-500 hover:bg-gray-100 hover:text-black', 'flex items-center gap-1 px-2 py-1 text-sm text-gray-500 hover:bg-gray-100 hover:text-black focus:outline-none',
{ {
'bg-gray-100 text-orange-600 hover:text-orange-700': isActive, 'bg-gray-100 text-orange-600 hover:text-orange-700': isActive,
'bg-transparent text-gray-500 hover:text-black': !isActive, 'bg-transparent text-gray-500 hover:text-black': !isActive,

@ -49,12 +49,14 @@ const githubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/maste
<div class='container'> <div class='container'>
<ProjectTabs projectId={projectId} activeTab='solutions' /> <ProjectTabs projectId={projectId} activeTab='solutions' />
<div class='mb-4 overflow-hidden rounded-lg border bg-white p-5'> <div class='mb-4 overflow-hidden rounded-lg border bg-white p-3 sm:p-5'>
<div class='relative mb-5'> <div class='relative mb-5'>
<h1 class='mb-1 text-xl font-semibold'> <h1 class='mb-1 text-xl font-semibold'>
{projectData.title} Solutions {projectData.title} Solutions
</h1> </h1>
<p class='text-sm text-gray-500'>{projectData.description}</p> <p class='hidden text-sm text-gray-500 sm:block'>
{projectData.description}
</p>
</div> </div>
<ListProjectSolutions projectId={projectId} client:load /> <ListProjectSolutions projectId={projectId} client:load />

Loading…
Cancel
Save