Skip to content

Commit 787514b

Browse files
Merge pull request #234 from CodeForPhilly/develop
Release: v0.8.0
2 parents 707da8a + dc8b5e8 commit 787514b

19 files changed

Lines changed: 462 additions & 21 deletions

‎apps/web/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"clsx": "^2.1.1",
3131
"lucide-react": "^1.16.0",
3232
"marked": "^18.0.4",
33+
"qrcode.react": "^4.2.0",
3334
"radix-ui": "^1.4.3",
3435
"react": "^19.2.6",
3536
"react-dom": "^19.2.6",

‎apps/web/src/App.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ProjectsIndex } from '@/screens/ProjectsIndex';
1010
import { ProjectDetail } from '@/screens/ProjectDetail';
1111
import { ProjectEdit } from '@/screens/ProjectEdit';
1212
import { ProjectBuzzNew } from '@/screens/ProjectBuzzNew';
13+
import { ProjectTableCard } from '@/screens/ProjectTableCard';
1314
import { PeopleIndex } from '@/screens/PeopleIndex';
1415
import { PersonDetail } from '@/screens/PersonDetail';
1516
import { ProfileEdit } from '@/screens/ProfileEdit';
@@ -50,6 +51,7 @@ const router = createBrowserRouter([
5051
{ path: '/projects/create', element: <ProjectEdit mode="create" /> },
5152
{ path: '/projects/:slug', element: <ProjectDetail /> },
5253
{ path: '/projects/:slug/edit', element: <ProjectEdit mode="edit" /> },
54+
{ path: '/projects/:slug/table-card', element: <ProjectTableCard /> },
5355
{ path: '/projects/:slug/updates/:number', element: <ProjectDetail anchor="update" /> },
5456
{ path: '/projects/:slug/buzz/:buzzSlug', element: <ProjectDetail anchor="buzz" /> },
5557
{ path: '/projects/:slug/buzz/new', element: <ProjectBuzzNew /> },

‎apps/web/src/components/AppFooter.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,9 @@ export function AppFooter() {
177177
<h2 className="text-sm font-semibold text-foreground mb-3">Connect</h2>
178178
<ul className="space-y-2 text-sm text-muted-foreground mb-4">
179179
<li>
180-
<Link to="/chat" className="hover:text-foreground transition-colors">
180+
<a href="/chat" className="hover:text-foreground transition-colors">
181181
Slack
182-
</Link>
182+
</a>
183183
</li>
184184
</ul>
185185
<div className="flex items-center gap-3 flex-wrap">

‎apps/web/src/components/AppHeader.tsx‎

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { Separator } from '@/components/ui/separator';
2020
import { SearchBox } from '@/components/SearchBox';
2121
import { GitHubIcon } from '@/components/icons/GitHubIcon';
22+
import { SlackIcon } from '@/components/icons/SlackIcon';
2223
import { useAuth } from '@/hooks/useAuth';
2324

2425
const GITHUB_URL = 'https://github.com/CodeForPhilly';
@@ -192,12 +193,26 @@ const navLinkClass = ({ isActive }: { isActive: boolean }) =>
192193
isActive ? 'text-primary' : 'text-muted-foreground'
193194
}`;
194195

196+
function SlackLink() {
197+
return (
198+
// A plain anchor: /chat is a server redirect the router can't resolve
199+
// (specs/screens/chat.md).
200+
<Button asChild variant="ghost" size="icon-sm">
201+
<a
202+
href="/chat"
203+
target="_blank"
204+
rel="noopener noreferrer"
205+
aria-label="Join Code for Philly on Slack (opens in new tab)"
206+
>
207+
<SlackIcon />
208+
</a>
209+
</Button>
210+
);
211+
}
212+
195213
function GitHubLink() {
196214
return (
197-
// Desktop-only: between md and lg the header has no room for it (the
198-
// utility cluster would push "Help Wanted" onto two lines); the mobile
199-
// sheet carries its own GitHub row.
200-
<Button asChild variant="ghost" size="icon-sm" className="hidden lg:inline-flex">
215+
<Button asChild variant="ghost" size="icon-sm">
201216
<a
202217
href={GITHUB_URL}
203218
target="_blank"
@@ -245,7 +260,7 @@ export function AppHeader() {
245260
flex/gap spacing moves to the <ul>; the <li>s contribute none. */}
246261
<nav
247262
aria-label="Primary navigation"
248-
className="hidden md:block ml-4 flex-1"
263+
className="hidden lg:block ml-4 flex-1"
249264
>
250265
<ul className="flex items-center gap-2">
251266
<li>
@@ -274,12 +289,13 @@ export function AppHeader() {
274289
</ul>
275290
</nav>
276291

277-
{/* Desktop utility cluster: GitHub, search, auth, then the Volunteer
292+
{/* Desktop utility cluster: Slack, GitHub, search, auth, then the Volunteer
278293
CTA pinned rightmost (specs/behaviors/app-shell.md). */}
279294
<nav
280295
aria-label="Utility"
281-
className="hidden md:flex items-center gap-2 ml-auto"
296+
className="hidden lg:flex items-center gap-2 ml-auto"
282297
>
298+
<SlackLink />
283299
<GitHubLink />
284300
<SearchBox />
285301
<AuthControls />
@@ -293,7 +309,7 @@ export function AppHeader() {
293309
</nav>
294310

295311
{/* Mobile: auth + hamburger */}
296-
<div className="flex md:hidden items-center gap-2 ml-auto">
312+
<div className="flex lg:hidden items-center gap-2 ml-auto">
297313
<AuthControls />
298314
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
299315
<SheetTrigger asChild>
@@ -392,6 +408,18 @@ export function AppHeader() {
392408
</ul>
393409
<Separator />
394410
<ul className="flex flex-col gap-2">
411+
<li>
412+
<a
413+
href="/chat"
414+
target="_blank"
415+
rel="noopener noreferrer"
416+
className="block text-sm font-medium text-muted-foreground hover:text-primary"
417+
onClick={() => setMobileOpen(false)}
418+
>
419+
Slack
420+
<span className="sr-only"> (opens in new tab)</span>
421+
</a>
422+
</li>
395423
<li>
396424
<a
397425
href={GITHUB_URL}

‎apps/web/src/components/ProjectCard.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
7777
)}
7878
{project.links.chatChannel && (
7979
<Button asChild size="sm" variant="outline">
80-
<Link to={`/chat?channel=${encodeURIComponent(project.links.chatChannel)}`}>Chat</Link>
80+
<a href={`/chat?channel=${encodeURIComponent(project.links.chatChannel)}`}>Chat</a>
8181
</Button>
8282
)}
8383
{project.openHelpWantedCount > 0 && (
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/** Slack mark (monochrome), sized to sit inline with text and icon buttons. */
2+
export function SlackIcon() {
3+
return (
4+
<svg
5+
xmlns="http://www.w3.org/2000/svg"
6+
width="18"
7+
height="18"
8+
viewBox="0 0 24 24"
9+
fill="currentColor"
10+
aria-hidden="true"
11+
>
12+
<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z" />
13+
</svg>
14+
);
15+
}

‎apps/web/src/screens/EventsIndex.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ function UpcomingList({ isStaff }: { isStaff: boolean }) {
7474
<div className="py-12 text-center">
7575
<p className="text-muted-foreground">
7676
Nothing on the calendar yet.{' '}
77-
<Link to="/chat" className="text-primary hover:underline">
77+
<a href="/chat" className="text-primary hover:underline">
7878
Join the Slack
79-
</Link>{' '}
79+
</a>{' '}
8080
and watch #announcements — or check back soon.
8181
</p>
8282
{isStaff && (

‎apps/web/src/screens/ProjectDetail.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,9 +485,9 @@ export function ProjectDetail({ anchor }: ProjectDetailProps = {}) {
485485
)}
486486
{project.links.chatChannel && (
487487
<Button asChild variant="outline">
488-
<Link to={`/chat?channel=${encodeURIComponent(project.links.chatChannel)}`}>
488+
<a href={`/chat?channel=${encodeURIComponent(project.links.chatChannel)}`}>
489489
Chat Channel
490-
</Link>
490+
</a>
491491
</Button>
492492
)}
493493
</div>
@@ -603,6 +603,9 @@ export function ProjectDetail({ anchor }: ProjectDetailProps = {}) {
603603
>
604604
Share to Slack
605605
</Button>
606+
<Button asChild variant="outline">
607+
<Link to={`/projects/${slug}/table-card`}>Table card</Link>
608+
</Button>
606609
</div>
607610
</section>
608611

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { Link, useParams } from 'react-router';
3+
import { QRCodeSVG } from 'qrcode.react';
4+
import { Button } from '@/components/ui/button';
5+
import { api, ApiError } from '@/lib/api';
6+
7+
/**
8+
* Printable table sign for a project (specs/screens/project-table-card.md):
9+
* a QR code to the project page, where a newcomer can sign in and join.
10+
*/
11+
export function ProjectTableCard() {
12+
const { slug = '' } = useParams<{ slug: string }>();
13+
const projectQ = useQuery({
14+
queryKey: ['project', slug],
15+
queryFn: () => api.projects.get(slug),
16+
});
17+
18+
if (projectQ.isLoading) {
19+
return <div className="container mx-auto px-4 py-12 text-muted-foreground">Loading project…</div>;
20+
}
21+
22+
if (projectQ.isError) {
23+
const err = projectQ.error;
24+
if (err instanceof ApiError && err.status === 404) {
25+
return (
26+
<div className="container mx-auto px-4 py-16 text-center">
27+
<h1 className="text-2xl font-bold mb-2">Project not found</h1>
28+
<p className="text-muted-foreground mb-6">No project with the slug “{slug}” exists.</p>
29+
<Link to="/projects" className="text-primary hover:underline">
30+
← Browse all projects
31+
</Link>
32+
</div>
33+
);
34+
}
35+
return <div className="container mx-auto px-4 py-12 text-destructive">Failed to load project.</div>;
36+
}
37+
38+
const project = projectQ.data!.data;
39+
// The origin the card was opened on, so production prints codeforphilly.org
40+
// and a test instance prints its own host.
41+
const url = `${window.location.origin}/projects/${project.slug}`;
42+
const displayUrl = url.replace(/^https?:\/\//, '');
43+
44+
return (
45+
<div className="container mx-auto px-4 py-8 print:p-0">
46+
<div className="flex items-center justify-center gap-3 mb-6 print:hidden">
47+
<Button onClick={() => window.print()}>Print</Button>
48+
<Button asChild variant="outline">
49+
<Link to={`/projects/${project.slug}`}>Back to project</Link>
50+
</Button>
51+
</div>
52+
53+
<article className="mx-auto max-w-2xl rounded-lg border border-border bg-white text-black px-8 py-12 text-center print:max-w-none print:border-0 print:rounded-none print:py-0">
54+
<img
55+
src="/img/logo-horizontal.svg"
56+
alt="Code for Philly"
57+
className="mx-auto h-14 mb-10 print:h-20"
58+
/>
59+
<h1 className="text-4xl font-bold leading-tight mb-3 print:text-5xl">{project.title}</h1>
60+
{project.summary && <p className="text-lg text-neutral-700 mb-8 line-clamp-2">{project.summary}</p>}
61+
<QRCodeSVG
62+
value={url}
63+
size={288}
64+
marginSize={0}
65+
title={`QR code for ${displayUrl}`}
66+
className="mx-auto my-8 h-[3in] w-[3in] print:my-12 print:h-[4in] print:w-[4in]"
67+
/>
68+
<p className="text-3xl font-semibold mb-2 print:text-4xl">Scan to join this project</p>
69+
<p className="text-lg font-mono text-neutral-700 break-all">{displayUrl}</p>
70+
{project.links.chatChannel && (
71+
<p className="text-lg mt-6">
72+
Chat: <span className="font-semibold">#{project.links.chatChannel}</span> on the Code
73+
for Philly Slack
74+
</p>
75+
)}
76+
</article>
77+
</div>
78+
);
79+
}

‎apps/web/src/screens/Volunteer.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function Volunteer() {
5252
We coordinate everything in our Slack workspace.
5353
</p>
5454
<Button asChild variant="outline" size="sm">
55-
<Link to="/chat">Open Slack →</Link>
55+
<a href="/chat">Open Slack →</a>
5656
</Button>
5757
</div>
5858
<div className="rounded-lg border border-border bg-card p-5">

0 commit comments

Comments
 (0)