Skip to content

Commit 3b5a74f

Browse files
committed
fixed responsiveness for mobile view
1 parent 8ef5ce9 commit 3b5a74f

2 files changed

Lines changed: 265 additions & 34 deletions

File tree

src/components/Collaboration/Collaboration.css

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,105 @@ body {
294294
}
295295
#root, .job-landing { min-height: 100vh; overflow: visible; }
296296
.user-collaboration-container { max-height: none; overflow: visible; }
297+
298+
/* ---------- Modal layout & typography ---------- */
299+
/* Card must be relative so the close button stays inside */
300+
.modal__card { position: relative; }
301+
302+
/* Header is now a simple vertical stack */
303+
.modal__header--stack {
304+
display: flex;
305+
flex-direction: column;
306+
align-items: flex-start;
307+
gap: 10px;
308+
margin-bottom: 12px;
309+
padding-right: 48px; /* space for the close button */
310+
}
311+
312+
/* Full-width, readable title */
313+
.modal__title {
314+
margin: 0;
315+
font-size: clamp(28px, 3.6vw, 48px);
316+
line-height: 1.12;
317+
letter-spacing: -0.01em;
318+
width: 100%;
319+
word-break: normal;
320+
overflow-wrap: anywhere;
321+
}
322+
323+
/* Meta chips under the title */
324+
.modal__meta {
325+
display: flex;
326+
flex-wrap: wrap;
327+
gap: 8px;
328+
}
329+
.chip {
330+
display: inline-flex;
331+
align-items: center;
332+
padding: 4px 10px;
333+
border-radius: 999px;
334+
border: 1px solid var(--ring, #e5e7eb);
335+
background: var(--card, #f7f7f9);
336+
font-size: 12px;
337+
color: var(--text, #111);
338+
}
339+
340+
/* Close button INSIDE the card, top-right */
341+
.modal__close-btn {
342+
position: absolute;
343+
top: 12px;
344+
right: 12px;
345+
width: 36px;
346+
height: 36px;
347+
border-radius: 10px;
348+
border: 1px solid #e5e7eb;
349+
background: #ffffff;
350+
color: #111;
351+
font-size: 22px;
352+
line-height: 1;
353+
cursor: pointer;
354+
box-shadow: 0 4px 12px rgba(0,0,0,.12);
355+
}
356+
357+
/* Intro paragraph below the heading */
358+
.modal__about {
359+
margin: 8px 0 6px;
360+
color: #4b5563; /* gray-600 */
361+
line-height: 1.5;
362+
font-size: 15px;
363+
}
364+
.modal__about a { color: #2563eb; text-decoration: underline; }
365+
366+
/* Body content spacing */
367+
.modal__article { margin: 10px 0 4px; }
368+
.modal__section { margin: 16px 0; }
369+
.modal__section h4 { margin: 0 0 8px; font-size: 18px; line-height: 1.35; }
370+
.modal__body p { margin: 0 0 10px; color: #374151; }
371+
.modal__body ul { padding-left: 1.25rem; margin: 8px 0 16px; }
372+
.modal__body li { margin: 6px 0; }
373+
374+
/* Image grid stays contained */
375+
.modal__image-grid {
376+
display: grid;
377+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
378+
gap: 8px;
379+
margin-top: 12px;
380+
}
381+
.modal__image-grid img {
382+
width: 100%;
383+
height: auto;
384+
border-radius: 8px;
385+
border: 1px solid #e5e7eb;
386+
}
387+
388+
/* Footer is simple now */
389+
.modal__footer {
390+
display: flex;
391+
justify-content: flex-start;
392+
margin-top: 12px;
393+
}
394+
395+
/* Responsive: nothing fancy required; stacked header already behaves well */
396+
@media (max-width: 560px) {
397+
.modal__title { font-size: clamp(22px, 6vw, 30px); }
398+
}

src/components/Collaboration/Collaboration.jsx

Lines changed: 163 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -147,44 +147,121 @@ const QUESTIONS = [
147147

148148
/* ------------------------ Rich “Learn More” modal ------------------------ */
149149
/** Inline styles are provided as a fallback to guarantee visibility even if CSS is missing/overridden. */
150+
/* -------------------- modal helpers -------------------- */
151+
const toList = val => {
152+
if (!val) return [];
153+
if (Array.isArray(val))
154+
return val
155+
.map(String)
156+
.map(s => s.trim())
157+
.filter(Boolean);
158+
// Accept newline or bullet-prefixed text (•, -, *)
159+
return String(val)
160+
.split(/\r?\n|[\u2022]|^\s*-\s+|^\s*\*\s+/gm)
161+
.map(s => s.trim())
162+
.filter(Boolean);
163+
};
164+
165+
const firstVal = (obj, keys) => {
166+
for (const k of keys) {
167+
if (obj && obj[k] != null && String(obj[k]).trim() !== '') return obj[k];
168+
}
169+
return null;
170+
};
171+
172+
const isReactish = ad =>
173+
/\b(react|mern|frontend)\b/i.test(`${ad?.title || ''} ${ad?.category || ''}`);
174+
175+
const buildSections = ad => {
176+
const reqTitle = isReactish(ad) ? 'React.js Position Requirements:' : 'Position Requirements:';
177+
const groups = [
178+
{ title: 'Role Responsibilities', keys: ['responsibilities', 'whatYoullDo', 'expectations'] },
179+
{
180+
title: reqTitle,
181+
keys: ['requirements', 'positionRequirements', 'required', 'mustHave', 'reactRequirements'],
182+
},
183+
{
184+
title: 'Additional Beneficial Qualifications:',
185+
keys: ['qualifications', 'beneficialQualifications', 'niceToHave', 'preferred'],
186+
},
187+
{
188+
title: 'Availability & Commitment',
189+
keys: ['commitment', 'availability', 'hours', 'timeRequirements'],
190+
},
191+
{ title: 'Tech / Tools We Use', keys: ['stack', 'techStack', 'tools', 'technologies'] },
192+
{ title: 'Benefits & What You’ll Gain', keys: ['benefits', 'perks'] },
193+
];
194+
195+
return groups
196+
.map(g => ({ title: g.title, items: toList(firstVal(ad, g.keys)) }))
197+
.filter(sec => sec.items.length > 0);
198+
};
199+
150200
function DescModal({ ad, onClose }) {
151201
if (!ad) return null;
152202

203+
// --- derive heading: "Seeking Experienced {Job Name}" ---
204+
const stripPrefix = s =>
205+
String(s || '')
206+
.replace(/^seeking\s+experienced\s+/i, '')
207+
.trim();
208+
const jobName = stripPrefix(ad.title || ad.category || 'Position');
209+
const heading = `Seeking Experienced ${jobName}`;
210+
211+
// rich HTML from API (optional)
153212
const html = ad.longHtml || ad.html;
154213

214+
// One Community blurb (can be overridden by backend via ad.aboutOneCommunity)
215+
const aboutOneCommunity =
216+
ad.aboutOneCommunity ||
217+
`One Community is a 100%-volunteer global sustainability organization. We're a software team of over 100 developers working on the 5 phases of this open source team management software: ` +
218+
`<a href="https://www.onecommunityglobal.org/highest-good-network/" target="_blank" rel="noopener noreferrer">www.onecommunityglobal.org/highest-good-network/</a>`;
219+
220+
// build content groups (Requirements, Responsibilities, etc.)
221+
const sections = buildSections(ad);
222+
223+
// --- styles (kept inline for resilience) ---
155224
const outerStyle = {
156225
position: 'fixed',
157226
inset: 0,
158227
display: 'grid',
159228
placeItems: 'center',
160229
zIndex: 9999,
161230
};
162-
const backdropStyle = {
163-
position: 'absolute',
164-
inset: 0,
165-
background: 'rgba(0,0,0,.45)',
166-
};
231+
const backdropStyle = { position: 'absolute', inset: 0, background: 'rgba(0,0,0,.45)' };
167232
const cardStyle = {
168233
position: 'relative',
169234
zIndex: 1,
170235
width: 'min(92vw, 1100px)',
171236
maxHeight: '80vh',
172237
display: 'flex',
173238
flexDirection: 'column',
174-
background: 'var(--surface, #fff)',
175-
color: 'var(--text, #111)',
176-
border: '1px solid var(--ring, #e5e7eb)',
177-
borderRadius: 'var(--radius, 12px)',
178-
boxShadow: 'var(--shadow, 0 6px 16px rgba(0,0,0,.18))',
179-
padding: 'var(--space-5, 20px)',
239+
background: 'var(--surface,#fff)',
240+
color: 'var(--text,#111)',
241+
border: '1px solid var(--ring,#e5e7eb)',
242+
borderRadius: 'var(--radius,12px)',
243+
boxShadow: 'var(--shadow,0 6px 16px rgba(0,0,0,.18))',
244+
padding: 'var(--space-5,20px)',
180245
};
181246
const bodyStyle = { flex: 1, overflow: 'auto', paddingRight: 8 };
247+
const chipRowStyle = { display: 'flex', flexWrap: 'wrap', gap: 8, margin: '8px 0 12px' };
248+
const chipStyle = {
249+
display: 'inline-flex',
250+
alignItems: 'center',
251+
padding: '4px 10px',
252+
borderRadius: 999,
253+
border: '1px solid var(--ring,#e5e7eb)',
254+
background: 'var(--card,#f7f7f9)',
255+
fontSize: 12,
256+
};
257+
const sectionStyle = { marginBottom: 16 };
258+
const listStyle = { paddingLeft: '1.25rem', margin: '8px 0 16px' };
182259

183260
const Section = ({ title, items }) =>
184261
items?.length ? (
185-
<section>
262+
<section style={sectionStyle}>
186263
<h4>{title}</h4>
187-
<ul>
264+
<ul style={listStyle}>
188265
{items.map((it, idx) => (
189266
<li key={idx}>{it}</li>
190267
))}
@@ -199,6 +276,26 @@ function DescModal({ ad, onClose }) {
199276
}
200277
};
201278

279+
// Meta chips under title
280+
const chips = [];
281+
if (ad.category) chips.push(ad.category);
282+
if (ad.location) chips.push(ad.location);
283+
if (ad.timezone) chips.push(ad.timezone);
284+
const hours = ad.commitmentHours || ad.hours;
285+
if (hours) chips.push(`${hours} hrs/wk`);
286+
if (ad.datePosted) {
287+
try {
288+
chips.push(new Date(ad.datePosted).toLocaleDateString());
289+
} catch {}
290+
}
291+
292+
const detailsLink =
293+
ad.jobDetailsLink ||
294+
(ad.category &&
295+
`https://www.onecommunityglobal.org/collaboration/seeking-${(
296+
ad.category || ''
297+
).toLowerCase()}`);
298+
202299
return (
203300
<div
204301
className="modal"
@@ -207,7 +304,6 @@ function DescModal({ ad, onClose }) {
207304
aria-modal="true"
208305
aria-labelledby="modal-title"
209306
>
210-
{/* Backdrop is operable via keyboard */}
211307
<div
212308
className="modal__backdrop"
213309
style={backdropStyle}
@@ -224,36 +320,65 @@ function DescModal({ ad, onClose }) {
224320
tabIndex={-1}
225321
role="document"
226322
>
227-
<header className="modal__header modal__header--center">
228-
<h2 id="modal-title">{ad.title || 'Position'}</h2>
323+
<header className="modal__header modal__header--stack">
324+
<h2 id="modal-title" className="modal__title">
325+
{heading}
326+
</h2>
327+
328+
{chips.length > 0 && (
329+
<div className="modal__meta" aria-label="Job meta">
330+
{chips.map((c, i) => (
331+
<span key={i} className="chip">
332+
{c}
333+
</span>
334+
))}
335+
</div>
336+
)}
229337
</header>
230338

231339
<div className="modal__body modal__body--scroll" style={bodyStyle}>
232-
{html ? (
340+
{/* One Community blurb immediately under the heading */}
341+
<p className="modal__about" dangerouslySetInnerHTML={{ __html: aboutOneCommunity }} />
342+
343+
{/* If backend supplies rich HTML, show it after the intro */}
344+
{html && (
233345
<article className="modal__article" dangerouslySetInnerHTML={{ __html: html }} />
234-
) : (
346+
)}
347+
348+
{/* Fallback role description if no HTML */}
349+
{!html && ad.description && (
235350
<article className="modal__article">
236-
{ad.description && <p>{ad.description}</p>}
237-
<Section title="React.js Position Requirements:" items={ad.requirements} />
238-
<Section title="Additional Beneficial Qualifications:" items={ad.qualifications} />
239-
{Array.isArray(ad.images) && ad.images.length > 0 && (
240-
<div className="modal__image-grid">
241-
{ad.images.map((src, i) => (
242-
<img key={i} src={src} alt={`${ad.title} visual ${i + 1}`} />
243-
))}
244-
</div>
245-
)}
351+
<h4>About the role</h4>
352+
<p>{ad.description}</p>
246353
</article>
247354
)}
355+
356+
{/* Auto-built sections (Requirements, Responsibilities, etc.) */}
357+
{sections.map((s, i) => (
358+
<section key={i} className="modal__section">
359+
<h4>{s.title}</h4>
360+
<ul>
361+
{s.items.map((it, idx) => (
362+
<li key={idx}>{it}</li>
363+
))}
364+
</ul>
365+
</section>
366+
))}
367+
368+
{/* Image grid + details link (unchanged) */}
369+
{Array.isArray(ad.images) && ad.images.length > 0 && (
370+
<div className="modal__image-grid">
371+
{ad.images.map((src, i) => (
372+
<img key={i} src={src} alt={`${ad.title || 'Position'} visual ${i + 1}`} />
373+
))}
374+
</div>
375+
)}
248376
</div>
249377

250-
<footer className="modal__footer modal__footer--space">
378+
<footer className="modal__footer">
251379
<button className="btn btn-primary" type="button" onClick={onClose}>
252380
Got it
253381
</button>
254-
<button className="modal__close-fab" type="button" onClick={onClose} aria-label="Close">
255-
X
256-
</button>
257382
</footer>
258383
</div>
259384
</div>
@@ -727,7 +852,9 @@ function Collaboration() {
727852
onChange={e => onApplyChange(e.target.value)}
728853
aria-label="Apply to a position"
729854
>
730-
<option value="">Software Engineer</option>
855+
<option value="" disabled>
856+
Select a position
857+
</option>
731858
{applyOptions.map(opt => (
732859
<option key={opt.value} value={opt.value}>
733860
{opt.label}
@@ -904,7 +1031,9 @@ function Collaboration() {
9041031
onChange={e => onApplyChange(e.target.value)}
9051032
aria-label="Apply to a position"
9061033
>
907-
<option value="">Software Engineer</option>
1034+
<option value="" disabled>
1035+
Select a position
1036+
</option>
9081037
{applyOptions.map(opt => (
9091038
<option key={opt.value} value={opt.value}>
9101039
{opt.label}

0 commit comments

Comments
 (0)