diff --git a/src/prototypes/onboarding-mentor/card-action-restyled/ClassPageWithBanner.tsx b/src/prototypes/onboarding-mentor/card-action-restyled/ClassPageWithBanner.tsx new file mode 100644 index 0000000..634c44a --- /dev/null +++ b/src/prototypes/onboarding-mentor/card-action-restyled/ClassPageWithBanner.tsx @@ -0,0 +1,153 @@ +import type { ReactNode } from 'react' +import { Button, Card } from '../../../kit' +import type { ClassGroup, Project } from '../../../fixtures' + +// A COPY of screens/classroom/EducatorClassPage, changed here rather than +// there. +// +// The shared screen renders its two bands — the white title block and the +// off-white content below it — as a fragment, with nothing between them and no +// prop to put anything there. Putting the "no members yet" alert where it +// belongs means opening that seam, and opening it in the shared screen would +// change the page for every other prototype using it. So it is copied in, per +// the repo rule. +// +// What is different from the shared screen: +// +// - A `banner` slot between the two bands. +// +// That is the whole change. Everything else is the shared screen as it stands, +// including the real page's furniture: "Class members" is the primary action, +// projects sit in a bordered list of pale rows, and a project name is bold body +// text rather than a link. Keep it that way — if the shared screen gains +// something, this copy should gain it too. + +interface Props { + classGroup: ClassGroup + projects: Project[] + memberCount: number + /** The blurb under the class title. */ + description?: string + /** + * Rendered between the title band and the content, which is where a page- + * level message belongs — after a mentor has read what page they are on, and + * before the thing they would otherwise start doing. + */ + banner?: ReactNode + onAddProject: () => void + onOpenProject: (projectId: string) => void + onCopyLink: () => void + onClassMembers: () => void + /** Projects students cannot see yet. */ + hiddenProjectIds?: string[] + /** The per-row overflow menu. Renders whether or not you wire it. */ + onProjectMenu?: (projectId: string) => void + /** The gear beside "Copy link". Renders whether or not you wire it. */ + onSettings?: () => void +} + +export function ClassPageWithBanner({ + classGroup, + projects, + memberCount, + description, + banner, + onAddProject, + onOpenProject, + onCopyLink, + onClassMembers, + hiddenProjectIds = [], + onProjectMenu, + onSettings, +}: Props) { + return ( + <> +
+
+

{classGroup.name}

+ {description &&

{description}

} +
+ +
+ {/* Primary on the live page. Getting young people into the class is + the job a mentor comes here to do. */} +
+
+ + {/* Padded to the same gutter as both bands, so the alert lines up with + the title above it and the Projects card below it. */} + {banner &&
{banner}
} + +
+ +
+
+

Projects

+

+ Projects are shared with students and contain starter code created by a teacher. +

+
+
+ + {projects.length === 0 ? ( +

No projects yet

+ ) : ( + + )} +
+
+ + ) +} diff --git a/src/prototypes/onboarding-mentor/card-action-restyled/DashboardWithClassroom.tsx b/src/prototypes/onboarding-mentor/card-action-restyled/DashboardWithClassroom.tsx new file mode 100644 index 0000000..e141dfd --- /dev/null +++ b/src/prototypes/onboarding-mentor/card-action-restyled/DashboardWithClassroom.tsx @@ -0,0 +1,154 @@ +import { Alert, Button } from '../../../kit' +import type { School } from '../../../fixtures' + +// A COPY of screens/code-club/MentorDashboard, changed here rather than there. +// +// The shared screen has no prop for another club action, and the change this +// prototype is making is not a prop — it is a reordering of the card's +// actions, which is the whole hypothesis. So it is copied in, per the repo +// rule: never edit a shared screen, copy it and change your copy. +// +// What is different from the live dashboard: +// +// - "Code Classroom" is the primary button on each club card, first in the row. +// - "Create an event" is gone from the card entirely. That is a real cost, not +// a detail — events are how a club fills its sessions and how Code Club +// counts activity — and it is the trade this variant is making on purpose. +// - Nothing on the card is labelled as a proposal. The page reads as if it +// shipped, so a mentor in a session reacts to the product rather than to a +// prototype. What is and is not real is in notes.md. +// +// Everything else is left exactly as the shared screen has it. + +interface Props { + clubsManaged: School[] + pendingRequests: string[] + onDismissRequest: (request: string) => void + onManageClub: (schoolId: string) => void + onViewPublicProfile: (schoolId: string) => void + onStartAClub: () => void + onFindAClub: () => void + clubsVolunteeredAt?: School[] + /** Which of these clubs already has a Code Classroom behind it. */ + isSetUp: (schoolId: string) => boolean + onCodeClassroom: (schoolId: string) => void +} + +export function DashboardWithClassroom({ + clubsManaged, + pendingRequests, + onDismissRequest, + onManageClub, + onViewPublicProfile, + onStartAClub, + onFindAClub, + clubsVolunteeredAt = [], + isSetUp, + onCodeClassroom, +}: Props) { + return ( +
+ + +
+

Your Clubs

+ + {pendingRequests.map((request) => ( + onDismissRequest(request)} + /> + ))} + +
+

Clubs you manage

+ +
+ + {clubsManaged.map((club) => ( +
+
{club.name}
+
+
+

+ Nearest 4.5 miles +

+

{club.venue ?? 'No venue set'}

+
+
+

{club.schedule ?? 'No sessions scheduled'}

+

+ {club.online ? 'Online Sessions' : 'In-person Sessions'} +

+
+
+
+ {/* The proposal. Primary, and first — a mentor arriving with + "what are we doing on Thursday?" should not have to know + the words "Manage club". "Create an event" is not here at + all: this variant gives the card's top slot to Code + Classroom rather than sharing it. */} +
+
+ ))} + +
+

Clubs you volunteer at

+ +
+ + {clubsVolunteeredAt.length === 0 ? ( +
+

You have not volunteered at any Clubs

+

+ Why not explore our resources on{' '} + recruiting volunteers for your Club +

+
+ ) : ( +
    + {clubsVolunteeredAt.map((club) => ( +
  • + {club.name} +
  • + ))} +
+ )} +
+
+ ) +} diff --git a/src/prototypes/onboarding-mentor/card-action-restyled/FeatureBox.tsx b/src/prototypes/onboarding-mentor/card-action-restyled/FeatureBox.tsx new file mode 100644 index 0000000..4371bba --- /dev/null +++ b/src/prototypes/onboarding-mentor/card-action-restyled/FeatureBox.tsx @@ -0,0 +1,61 @@ +import type { ReactNode } from 'react' + +interface Props { + /** + * A Material Symbols ligature name, e.g. "checklist". The font renders the + * text content as the glyph, so this IS the icon. + */ + icon: string + title: string + children: ReactNode +} + +/** + * A pale green panel with an icon, a bold title and a line underneath — what + * the three "what Code Classroom is" points are shown as, instead of bullets. + * + * Local to this prototype rather than added to kit/: the kit has no equivalent, + * and the repo rule is that a prototype adds to its own folder. If a second + * prototype wants one of these, copy it. + * + * Material Symbols Outlined is already loaded by the design system's + * stylesheet — the same family Surface uses for its breadcrumb chevron and + * safeguarding flag — so there is no font to add and no dependency to install. + * + * The icon is decorative and hidden from screen readers: the title next to it + * says the same thing, and a reader announcing the ligature name would just + * read "checklist" twice. + */ +export function FeatureBox({ icon, title, children }: Props) { + return ( +
+ + {/* Body size, bold — still an h3, so the heading structure survives + it reading at the same scale as the line underneath. */} +

+ {title} +

+

+ {children} +

+
+ ) +} diff --git a/src/prototypes/onboarding-mentor/card-action-restyled/meta.ts b/src/prototypes/onboarding-mentor/card-action-restyled/meta.ts new file mode 100644 index 0000000..44398a2 --- /dev/null +++ b/src/prototypes/onboarding-mentor/card-action-restyled/meta.ts @@ -0,0 +1,20 @@ +import type { PrototypeMeta } from '../../types' + +export const meta: PrototypeMeta = { + title: 'Club card action, restyled', + + owner: 'Sarah Tucker', + + // DRAFT — Sarah to correct. The flow is a straight copy of + // "Code Classroom as the club card action", so the route is not what is + // being tested here; only how the confirm screen is presented. + hypothesis: + 'The confirm screen is where a mentor decides whether Code Classroom is for them, and two dense cards of prose get skimmed. Presented as one centred explainer — a short claim per panel, each with an icon — a mentor will be able to say in their own words what Code Classroom is before they press yes, rather than agreeing and finding out afterwards.', + + status: 'sketch', + + // The same mentor as the prototype this copies, on purpose: two visual + // treatments of one route only compare if the club, the code and the class + // name are identical. + cast: { piAccount: 'pi-mentor-jo' }, +} diff --git a/src/prototypes/onboarding-mentor/card-action-restyled/notes.md b/src/prototypes/onboarding-mentor/card-action-restyled/notes.md new file mode 100644 index 0000000..f953df4 --- /dev/null +++ b/src/prototypes/onboarding-mentor/card-action-restyled/notes.md @@ -0,0 +1,104 @@ +A copy of **Code Classroom as the club card action** (Divya Mahadevan) with one +screen restyled. The route, the fixtures, the fake latency and every other step +are unchanged, on purpose — if more than the presentation varied, a session +could not tell which change caused the reaction. + +## What question is this answering? + +The `onboarding-mentor` lane, but not the part about where the entry point +lives. The prototype this copies already bets on that: Code Classroom as the +primary button on the club card. This one takes that bet as settled and asks +the next question down. + +**Does a mentor know what they have agreed to?** + +The confirm screen is the only place a mentor is told what Code Classroom is. +They arrive from a button on a dashboard, having possibly never heard the +product name, and one press later they have a school, a club code and a class. +If that screen gets skimmed, the route succeeds on the measure it set itself — +one click to a working Code Classroom — while leaving a mentor who cannot say +what they now own. + +## Why this approach + +Two dense cards of prose, stacked full width, is a layout people scroll past. +So the confirm screen is rebuilt as a single centred explainer, modelled on the +Experience CS → Code Classroom interstitial that already exists in the real +products: + +- **One card, not two.** Title inside it, narrow measure, floating on the page + green. It reads as a thing to be read rather than a form to get past. +- **Three green panels instead of bullets.** One claim each, an icon, a short + line. A bullet list invites skimming to the end; separated panels are harder + to skip without noticing you skipped them. +- **Primary action last, bottom right.** Matching the reference. It also puts + the button after the explanation rather than beside it. + +**Rejected:** a "Learn more" link out to a marketing page, as the reference has. +It solves the comprehension problem by moving it somewhere a mentor in a club +session will not go. If the explanation cannot fit on the screen that asks for +consent, the ask is in the wrong place. + +**Rejected:** a second confirm step. The one-press claim is the whole point of +the route being copied; adding a step would test a different thing. + +## What is not real + +- **The confirm screen does not exist in either product.** The entire thing is + a proposal. Nothing in Code Club today offers to create a Code Classroom + school for you. +- **There is no illustration.** The reference card carries a diagram of the two + products handing off, between the list and the buttons. Left out for now — + what it should show has not been designed, and an invented one would get + reacted to in a session as though it had been. Worth deciding before this + goes in front of anyone: the card currently explains Code Classroom in words + only. +- Automatic setup infers **one** class from the club's session time. Westlands + really runs a Scratch group and a Python group — the same honest limit as the + prototype this copies. +- **The "no members yet" alert is not in the product.** The class page itself + is the real, verified screen; the alert sitting between its two bands is this + prototype's addition, and so is the "Adding members" panel behind it. The + class page is copied into this folder as `ClassPageWithBanner` purely to open + a seam between the title band and the content — one added prop, nothing else + changed. +- **The steps in that panel are not a screen either.** Step 2, creating the + accounts, has no screen anywhere in this repo. The panel says so rather than + implying a flow exists behind it. + +## What I'd want to watch in testing + +- **At the moment they press yes:** ask them to say what Code Classroom is + without looking. That is the whole hypothesis, and it is the only question + here that the original prototype does not already answer. +- **Whether the green panels get read or counted.** Three panels may just be a + bulleted list with more furniture. If people's eyes go straight to the + button, the restyle has not earned its keep and the finding is worth as much + as a positive one. +- **"Creators".** The confirm card now says creators throughout — it is the + word Code Club uses. Two places still do not: the created screen says "an + account for each young person at your club", and Code Classroom itself will + say students the moment a mentor arrives in it. So the handover is where the + word changes, which is exactly where a mentor is least able to absorb it. + Worth watching whether anyone notices they have become a teacher with + students. +- **The word "school"**, still. A volunteer at a library is told they are + getting a school. The card no longer glosses it — the earlier draft said + "Code Classroom's word, not ours", which was the prototype apologising for + the product. Without the aside the screen is a fairer test of whether the + word actually lands, but it also means nobody is warned. This is the sharpest + edge on the screen, and the school page at the end says it again, in the + title, at full size. +- **Whether anyone opens "How do I add members?"** The alert names the blocker + on arrival; the action behind it is the test of whether naming it is enough. + If people read the alert and still press "Add project" first, the alert is + decoration. +- **Whether the alert reads as help or as failure.** "Your class has no members + yet" arrives seconds after a screen that said setup was done. It is either + the obvious next step or evidence that the one-press promise was oversold — + and which of those it is, is the most useful thing a session could tell us + about this whole route. + +## What we learned + +Not tested yet. diff --git a/src/prototypes/onboarding-mentor/card-action-restyled/prototype.tsx b/src/prototypes/onboarding-mentor/card-action-restyled/prototype.tsx new file mode 100644 index 0000000..7cae99a --- /dev/null +++ b/src/prototypes/onboarding-mentor/card-action-restyled/prototype.tsx @@ -0,0 +1,370 @@ +import { useEffect, useState, type CSSProperties } from 'react' +import { Alert, Button, Card, ProgressBar } from '../../../kit' +import { schoolsForMentor, type ClassGroup, type School } from '../../../fixtures' +import { ClassPageWithBanner } from './ClassPageWithBanner' +import { Surface } from '../../../surfaces' +import { meta } from './meta' +import { DashboardWithClassroom } from './DashboardWithClassroom' +import { FeatureBox } from './FeatureBox' + +// A mentor, signed in on codeclub.org. Everything below comes from the cast in +// meta.ts — Jo manages two clubs, so the dashboard has two cards and "which +// club am I setting up?" is answered by which card you press. +const CLUBS: School[] = schoolsForMentor(meta.cast?.piAccount ?? '') + +const DAYS = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', +] as const + +/** + * The class automatic setup would create, named from what codeclub.org already + * knows about the club's sessions. Derived, not hardcoded: change the cast and + * the club, the code and the class name all follow. + * + * Only one class, because that is all an automatic setup can honestly infer. + * Westlands really runs a Scratch group and a Python group — see notes. + */ +function autoClass(club: School): ClassGroup { + const day = DAYS.find((d) => club.schedule?.includes(d)) + return { + id: `auto-${club.id}`, + schoolId: club.id, + name: day ? `${day} club` : 'Club session', + kind: 'code-club', + studentIds: [], + } +} + +type Step = 'dashboard' | 'confirm' | 'creating' | 'created' | 'classroom' + +/** + * A section heading and the copy it introduces, held tight to each other. + * + * The card's own column gap is 16px, which is right between blocks but too + * loose between a heading and the sentence it belongs to. So a section is its + * own little column at 4px, and the 16px top margin — on top of the card's + * 16px — is what gives every section the same 32px above it. + */ +const SECTION: CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: 'var(--space-05)', + marginTop: 'var(--space-2)', +} + +/** + * The card layout both of this flow's own screens use: one narrow card + * centred on the page colour, everything inside it, actions bottom right. + * + * Shared between the confirm screen and the created screen so the two look + * like one route rather than two prototypes. Not in kit/ — a prototype adds to + * its own folder. + */ +const PAGE: CSSProperties = { + display: 'flex', + justifyContent: 'center', + padding: 'var(--space-4) var(--space-2)', +} +const COLUMN: CSSProperties = { width: '100%', maxWidth: 760 } +const CARD_STACK: CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: 'var(--space-2)', +} +const ACTIONS: CSSProperties = { justifyContent: 'flex-end', marginTop: 'var(--space-2)' } + +export default function DashboardPrimaryAction() { + const [step, setStep] = useState('dashboard') + /** Clubs that have been through setup, so the button changes on return. */ + const [setUpClubIds, setSetUpClubIds] = useState([]) + const [activeClubId, setActiveClubId] = useState(CLUBS[0].id) + /** What a route we have not built would have done. */ + const [notice, setNotice] = useState() + /** Whether the mentor has asked how members get added. */ + const [showHowTo, setShowHowTo] = useState(false) + + const club = CLUBS.find((c) => c.id === activeClubId) ?? CLUBS[0] + const classes = [autoClass(club)] + + // Fake latency, because "it set itself up" is a claim about how long a + // mentor waits, and a setup that returns instantly tests a different thing. + useEffect(() => { + if (step !== 'creating') return + const timer = setTimeout(() => { + setSetUpClubIds((ids) => (ids.includes(club.id) ? ids : [...ids, club.id])) + setStep('created') + }, 1800) + return () => clearTimeout(timer) + }, [step, club.id]) + + const noticeAlert = notice ? ( + setNotice(undefined)} /> + ) : null + + /** + * What setup makes, derived from the club. Shown before and after, so it + * stays tenseless — the confirm screen supplies "we'll create:" above it and + * the created screen supplies "What was set up". + * + * The values stay interpolated. Changing the cast has to move the club name, + * the code and the class name together, or the flow offers one club's code + * and then names a different club's session. + */ + const whatYouGet = ( +
    +
  • + A school called {club.name} +
  • +
  • + A club code {club.schoolCode} for creators to sign in +
  • +
  • + A class called {classes[0].name}, based on your session +
  • +
+ ) + + // The mentor is asked once, and asked nothing else. No form, no naming, no + // choosing whether the club is a school or a class — all of that is + // answerable from what Code Club already knows. + // + // THIS IS THE ONLY SCREEN THAT DIFFERS from the prototype this copies. Laid + // out like the Experience CS → Code Classroom interstitial: one narrow card + // centred on the page colour, everything inside it, and the two actions + // bottom right with the primary last. The three points about what Code + // Classroom is are green panels with an icon rather than bullets, because + // the bet is that the explanation is what gets skimmed. + // + // Inline styles rather than new classes: global.css is shared, and the repo + // rule is that a prototype changes nothing outside its own folder. + if (step === 'confirm') { + return ( + +
+
+ +
+

Set up Code Classroom for {club.name}?

+ +
+

What is Code Classroom?

+

+ Code Classroom is a free, safe environment for teaching and learning + computing. You can choose projects for your creators, create their accounts, + and see the work they save. +

+
+ + + Share projects with your club so creators know what to work on + + + Set up usernames and passwords without email addresses or parent sign-up + + + Creators can sign in again and continue where they left off + + +
+

What we'll set up

+

If you continue, we'll create:

+ {whatYouGet} +
+ +

+ Nothing will be shared with your club or made public. You can change these + names later. +

+ + {/* The reference card carries a diagram of the two products + between the list and the buttons. Left out for now — what it + should show has not been designed. */} + +
+
+
+
+
+
+
+ ) + } + + if (step === 'creating') { + return ( + +
+

Setting up Code Classroom

+ + +

This will take a few seconds. You do not need to do anything.

+
+
+
+ ) + } + + // Same card as the confirm screen, so arriving here reads as the other side + // of the question rather than a different product. The title moves inside + // the card, the list keeps the tight heading spacing, and the one action + // sits bottom right where "Set up Code Classroom" was a moment ago. + if (step === 'created') { + return ( + +
+
+ +
+

Your Code Classroom account has been created

+ +
+

What was set up

+ {whatYouGet} +
+ +

+ You sign in with the same Raspberry Pi account you use for Code Club — there is + no new password to remember. +

+

+ Not set up: an account for each young person at your club. Nothing on + codeclub.org knows who they are, so that part cannot be done for you. +

+ +
+
+
+
+
+
+
+ ) + } + + // Where a mentor lands: the real Code Classroom class page, in the empty + // state a mentor who has just been set up would actually find. + // + // The alert above it is THIS PROTOTYPE'S ADDITION and is not in the product. + // Setup makes a school, a code and a class and then stops — the creators are + // the one thing it cannot do, because nothing on codeclub.org knows who they + // are. A mentor who does not realise that leaves with a class nobody can get + // into, and finds out in front of a room of young people. So the blocker is + // stated on arrival, and the alert offers the next step rather than just + // naming the problem. + if (step === 'classroom') { + return ( + +
+ {noticeAlert} +
+ } + onAddProject={() => + setNotice( + 'Not built here — getting a Code Club project into Code Classroom is the importing lane’s question.', + ) + } + onOpenProject={() => {}} + onCopyLink={() => + setNotice( + 'Copy link shares the class. It skips the club code screen, but a creator still needs an account you made for them.', + ) + } + onClassMembers={() => + setNotice( + 'Not built here — creating the accounts is the step this flow stops at. See "How do I add members?" above.', + ) + } + /> + +
+ ) + } + + return ( + + {}} + onManageClub={() => {}} + onViewPublicProfile={() => {}} + onStartAClub={() => {}} + onFindAClub={() => {}} + isSetUp={(schoolId) => setUpClubIds.includes(schoolId)} + onCodeClassroom={(schoolId) => { + setActiveClubId(schoolId) + // Already set up? Straight in. Otherwise the mentor is asked first. + setStep(setUpClubIds.includes(schoolId) ? 'classroom' : 'confirm') + }} + /> + + ) +}