Skip to content

Commit f2a7bfe

Browse files
author
raineer
committed
pull
2 parents c935c8e + 9e1a719 commit f2a7bfe

4 files changed

Lines changed: 153 additions & 63 deletions

File tree

File renamed without changes.

‎apps/codebility/app/applicant/waiting/_components/applicantStep2.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ function PostReadInstructions({
152152
if (user?.display_position.includes("UI/UX Designer") === false) {
153153
// Validate the fork url
154154
const urlPattern =
155-
/^(https?:\/\/)?(www\.)?github\.com\/[a-zA-Z0-9_-]+\/codebility-assessment(\/.*)?$/;
155+
/^(https?:\/\/)?(www\.)?github\.com\/[a-zA-Z0-9_-]+\/codebility-assessment(\.git)?(\/.*)?$/;
156156

157157
if (!urlPattern.test(data.fork_url)) {
158158
throw new Error(
@@ -285,7 +285,7 @@ function PostSubmitted({
285285
if (user?.display_position.includes("UI/UX Designer") === false) {
286286
// Validate the fork url
287287
const urlPattern =
288-
/^(https?:\/\/)?(www\.)?github\.com\/[a-zA-Z0-9_-]+\/codebility-assessment(\/.*)?$/;
288+
/^(https?:\/\/)?(www\.)?github\.com\/[a-zA-Z0-9_-]+\/codebility-assessment(\.git)?(\/.*)?$/;
289289

290290
if (!urlPattern.test(data.fork_url)) {
291291
throw new Error(

‎apps/codebility/app/home/my-team/AddMembersModal.tsx‎

Lines changed: 150 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
DialogTitle,
1111
} from "@/components/ui/dialog";
1212
import { Codev, InternalStatus } from "@/types/home/codev";
13-
import { Search, Users } from "lucide-react";
13+
import { Search, Users, X } from "lucide-react";
1414
import toast from "react-hot-toast";
1515
import { useModal } from "@/hooks/use-modal-users";
1616
import { createClientClientComponent } from "@/utils/supabase/client";
@@ -43,51 +43,71 @@ const calculateYearsFromExperience = (workExperience: any[]): number => {
4343
return Math.round(totalYears);
4444
};
4545

46-
// ✅ Responsive Avatar Component
46+
// ✅ Responsive Avatar Component — X button visible only on hover
4747
const TeamMemberAvatar = ({
4848
imageUrl,
4949
name,
5050
position,
5151
size = 32,
5252
member,
53-
onClick
53+
onClick,
54+
onRemove,
5455
}: {
5556
imageUrl?: string | null;
5657
name: string;
5758
position?: string;
5859
size?: number;
5960
member?: Codev;
6061
onClick?: (member: Codev) => void;
62+
onRemove?: (member: Codev) => void;
6163
}) => (
62-
<div className="flex flex-col items-center space-y-1 w-full">
63-
<div
64-
className={`relative flex-shrink-0 rounded-full overflow-hidden ring-1 sm:ring-2 ring-customBlue-400 ${
65-
member && onClick ? 'cursor-pointer hover:ring-customBlue-300 transition-all duration-200' : ''
66-
}`}
67-
style={{ width: size, height: size }}
68-
onClick={(e) => {
69-
e.stopPropagation();
70-
if (member && onClick) {
71-
onClick(member);
72-
}
73-
}}
74-
title={member ? "Click to view profile" : undefined}
75-
>
76-
<img
77-
src={
78-
imageUrl ||
79-
"https://codebility-cdn.pages.dev/assets/images/default-avatar-200x200.jpg"
80-
}
81-
alt={name}
82-
className="w-full h-full object-cover"
83-
onError={(e) => {
84-
const target = e.target as HTMLImageElement;
85-
target.src = "https://codebility-cdn.pages.dev/assets/images/default-avatar-200x200.jpg";
64+
<div className="flex flex-col items-center space-y-1 w-full">
65+
{/* ✅ Extra padding so the X button (positioned outside the circle) is never clipped */}
66+
<div className="group relative" style={{ width: size + 8, height: size + 8, padding: 4 }}>
67+
<div
68+
className={`relative rounded-full overflow-hidden ring-1 sm:ring-2 ring-customBlue-400 w-full h-full ${
69+
member && onClick ? 'cursor-pointer hover:ring-customBlue-300 transition-all duration-200' : ''
70+
}`}
71+
onClick={(e) => {
72+
e.stopPropagation();
73+
if (member && onClick) {
74+
onClick(member);
75+
}
8676
}}
87-
/>
77+
title={member ? "Click to view profile" : undefined}
78+
>
79+
<img
80+
src={
81+
imageUrl ||
82+
"https://codebility-cdn.pages.dev/assets/images/default-avatar-200x200.jpg"
83+
}
84+
alt={name}
85+
className="w-full h-full object-cover"
86+
onError={(e) => {
87+
const target = e.target as HTMLImageElement;
88+
target.src = "https://codebility-cdn.pages.dev/assets/images/default-avatar-200x200.jpg";
89+
}}
90+
/>
91+
</div>
92+
93+
{/* ✅ X Remove Button — only visible on hover via group-hover */}
94+
{member && onRemove && (
95+
<button
96+
onClick={(e) => {
97+
e.stopPropagation();
98+
onRemove(member);
99+
}}
100+
title="Remove member"
101+
className="absolute top-0 right-0 z-20 flex items-center justify-center rounded-full bg-red-500 hover:bg-red-600 text-white shadow-md opacity-0 group-hover:opacity-100 transition-opacity duration-150"
102+
style={{ width: Math.max(14, size * 0.38), height: Math.max(14, size * 0.38) }}
103+
>
104+
<X style={{ width: Math.max(8, size * 0.22), height: Math.max(8, size * 0.22) }} strokeWidth={3} />
105+
</button>
106+
)}
88107
</div>
108+
89109
<div className="text-center w-full">
90-
<div className="text-xs text-white font-medium truncate w-full leading-tight mt-2">
110+
<div className="text-xs text-white font-medium truncate w-full leading-tight mt-1">
91111
{name}
92112
</div>
93113
{position && (
@@ -180,15 +200,17 @@ const TeamLeaderDisplay = ({
180200
</div>
181201
);
182202

183-
// ✅ Team Members Grid
203+
// ✅ Team Members Grid — flex-based scroll, hover-reveal scrollbar, no cut-offs
184204
const TeamMembersGrid = ({
185205
members,
186206
currentMemberIds,
187-
onProfileClick
207+
onProfileClick,
208+
onRemoveMember,
188209
}: {
189210
members: Codev[];
190211
currentMemberIds: string[];
191212
onProfileClick?: (member: Codev) => void;
213+
onRemoveMember?: (member: Codev) => void;
192214
}) => {
193215
const getAvatarSize = () => {
194216
if (typeof window !== 'undefined') {
@@ -200,32 +222,60 @@ const TeamMembersGrid = ({
200222
};
201223

202224
return (
203-
<div className="h-full flex flex-col">
225+
<div className="flex flex-col min-h-0 flex-1">
204226
<h4 className="text-base sm:text-lg font-semibold text-white mb-1 mt-[-15px]">Team Members</h4>
205-
<div className="mb-1 sm:mb-2 md:mt-2">
227+
<div className="mb-1 sm:mb-2 md:mt-2 flex-shrink-0">
206228
<span className="text-white text-xs sm:text-sm">{members.length} members</span>
207229
</div>
208-
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-1 sm:gap-2 w-full mt-6">
209-
{members.map((member) => {
210-
const isNew = !currentMemberIds.includes(member.id);
211-
return (
212-
<div key={member.id} className="w-full flex flex-col items-center relative mb-5">
213-
{isNew && (
214-
<div className="absolute -top-1 -right-1 bg-green-500 text-white text-xs px-1 rounded-full z-10">
215-
New
216-
</div>
217-
)}
218-
<TeamMemberAvatar
219-
imageUrl={member.image_url}
220-
name={member.first_name}
221-
position={member.display_position}
222-
size={getAvatarSize()}
223-
member={member}
224-
onClick={onProfileClick}
225-
/>
226-
</div>
227-
);
228-
})}
230+
231+
{/*
232+
- flex-1 + min-h-0 lets the scroll container grow to fill whatever space
233+
the parent left panel gives it — no hardcoded heights, no excess space
234+
- overflowY:'scroll' always reserves the scrollbar gutter → zero layout shift on hover
235+
- scrollbar track/thumb are transparent by default, visible only on hover via CSS
236+
- inner grid overflow:visible so X buttons and avatar rings never clip
237+
*/}
238+
<div
239+
className="mt-2 members-scroll flex-1 min-h-0"
240+
style={{ overflowY: 'scroll', overflowX: 'hidden' }}
241+
>
242+
<div
243+
className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 w-full"
244+
style={{
245+
columnGap: '8px',
246+
rowGap: '24px',
247+
paddingTop: '8px',
248+
paddingBottom: '16px',
249+
paddingLeft: '4px',
250+
paddingRight: '4px',
251+
overflow: 'visible',
252+
}}
253+
>
254+
{members.map((member) => {
255+
const isNew = !currentMemberIds.includes(member.id);
256+
return (
257+
<div key={member.id} className="w-full flex flex-col items-center relative">
258+
{isNew && (
259+
<div
260+
className="absolute bg-green-500 text-white text-xs px-1 rounded-full z-30 pointer-events-none"
261+
style={{ top: 2, right: 2 }}
262+
>
263+
New
264+
</div>
265+
)}
266+
<TeamMemberAvatar
267+
imageUrl={member.image_url}
268+
name={member.first_name}
269+
position={member.display_position}
270+
size={getAvatarSize()}
271+
member={member}
272+
onClick={onProfileClick}
273+
onRemove={onRemoveMember}
274+
/>
275+
</div>
276+
);
277+
})}
278+
</div>
229279
</div>
230280
</div>
231281
);
@@ -237,15 +287,17 @@ const ProjectPreview = ({
237287
teamLead,
238288
selectedMembers,
239289
currentMemberIds,
240-
onProfileClick
290+
onProfileClick,
291+
onRemoveMember,
241292
}: {
242293
projectName: string;
243294
teamLead: SimpleMemberData | null;
244295
selectedMembers: Codev[];
245296
currentMemberIds: string[];
246297
onProfileClick?: (member: Codev) => void;
298+
onRemoveMember?: (member: Codev) => void;
247299
}) => (
248-
<div className="h-full flex flex-col p-1 sm:p-2 gap-y-6">
300+
<div className="h-full flex flex-col p-1 sm:p-2 gap-y-4 overflow-hidden">
249301
<div className="flex-shrink-0">
250302
<h3 className="text-base sm:text-lg font-semibold text-white mb-1">Project Name</h3>
251303
<div className="flex items-center gap-2 sm:gap-3 mt-2">
@@ -278,11 +330,12 @@ const ProjectPreview = ({
278330
/>
279331
</div>
280332

281-
<div className="flex-1 min-h-0 mr-8">
333+
<div className="flex-1 min-h-0 mr-8 flex flex-col">
282334
<TeamMembersGrid
283335
members={selectedMembers}
284336
currentMemberIds={currentMemberIds}
285337
onProfileClick={onProfileClick}
338+
onRemoveMember={onRemoveMember}
286339
/>
287340
</div>
288341
</div>
@@ -317,7 +370,7 @@ const AddMembersModal = ({
317370
const [isLoadingMembers, setIsLoadingMembers] = useState(false);
318371
const [loadError, setLoadError] = useState<string | null>(null);
319372

320-
// ✅ FIXED: Paginated fetch to bypass Supabase's 1000-row default limit
373+
// ✅ Paginated fetch to bypass Supabase's 1000-row default limit
321374
const fetchAllCodevs = useCallback(async (): Promise<Codev[]> => {
322375
if (!supabase) {
323376
console.error('Supabase client not available');
@@ -346,7 +399,6 @@ const AddMembersModal = ({
346399

347400
allData = [...allData, ...data];
348401

349-
// If we got fewer rows than PAGE_SIZE, we've reached the last page
350402
if (data.length < PAGE_SIZE) break;
351403

352404
from += PAGE_SIZE;
@@ -532,6 +584,11 @@ const AddMembersModal = ({
532584
});
533585
}, []);
534586

587+
// ✅ Remove member via X button on avatar
588+
const handleRemoveMember = useCallback((member: Codev) => {
589+
setSelectedMembers(prev => prev.filter(m => m.id !== member.id));
590+
}, []);
591+
535592
const isSelected = useCallback((userId: string) =>
536593
selectedMembers.some(m => m.id === userId),
537594
[selectedMembers]
@@ -644,6 +701,36 @@ const AddMembersModal = ({
644701
if (!isOpen) return null;
645702

646703
return (
704+
<>
705+
<style>{`
706+
/* Members grid: scrollbar gutter always reserved (no layout shift), thumb hidden until hover */
707+
.members-scroll {
708+
scrollbar-width: thin;
709+
scrollbar-color: transparent transparent;
710+
}
711+
.members-scroll:hover {
712+
scrollbar-color: rgba(99, 120, 255, 0.45) transparent;
713+
}
714+
.members-scroll::-webkit-scrollbar {
715+
width: 4px;
716+
background: transparent;
717+
}
718+
.members-scroll::-webkit-scrollbar-track {
719+
background: transparent;
720+
border: none;
721+
box-shadow: none;
722+
}
723+
.members-scroll::-webkit-scrollbar-thumb {
724+
background-color: transparent;
725+
border-radius: 4px;
726+
border: none;
727+
box-shadow: none;
728+
}
729+
.members-scroll:hover::-webkit-scrollbar-thumb {
730+
background-color: rgba(99, 120, 255, 0.45);
731+
}
732+
`}</style>
733+
<div style={{display:'contents'}}>
647734
<Dialog open={isOpen} onOpenChange={onClose}>
648735
<DialogContent className="max-w-full w-[85vw] sm:w-[80vw] lg:w-[80vw] h-[80vh] flex flex-col dark:bg-slate-950 border-2 border-slate-300 dark:border-blue-900/60 p-4 sm:p-0">
649736
<DialogHeader className="flex-shrink-0 px-3 sm:px-6 pt-1 pb-1">
@@ -654,13 +741,14 @@ const AddMembersModal = ({
654741

655742
<div className="flex flex-col lg:flex-row flex-1 min-h-0 overflow-hidden">
656743
{/* Left Panel - Project Preview */}
657-
<div className="w-full lg:w-3/5 border-b lg:border-b-0 lg:border-r border-white/30 dark:border-white/20 pr-0 md:pr-6">
744+
<div className="w-full lg:w-3/5 border-b lg:border-b-0 lg:border-r border-white/30 dark:border-white/20 pr-0 md:pr-6 h-full overflow-hidden">
658745
<ProjectPreview
659746
projectName={project.name}
660747
teamLead={teamLeadData}
661748
selectedMembers={selectedMembers}
662749
currentMemberIds={currentMembers.map(m => m.id)}
663750
onProfileClick={handleProfileClick}
751+
onRemoveMember={handleRemoveMember}
664752
/>
665753
</div>
666754

@@ -821,6 +909,8 @@ const AddMembersModal = ({
821909
</div>
822910
</DialogContent>
823911
</Dialog>
912+
</div>
913+
</>
824914
);
825915
};
826916

PR_NOTES.md renamed to apps/codebility/app/home/ticket-support/TICKETSUPPORT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Pull Request: Ticket Management System (TMS) Enhancements
1+
# Ticket Management System (TMS) Enhancements
22

33
## 1. Overview
44
This PR implements and refines the **Ticket Management System (TMS)** and the **Public Ticket Support Form**. The system provides a centralized dashboard for support administrators and a high-performance, accessible form for all users, with a focus on privacy and production-ready UX.

0 commit comments

Comments
 (0)