Skip to content

Commit

Permalink
add explainer for trip tick
Browse files Browse the repository at this point in the history
  • Loading branch information
a-type committed May 4, 2024
1 parent 21a1923 commit 38e174e
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 74 deletions.
2 changes: 2 additions & 0 deletions apps/trip-tick/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Toaster } from 'react-hot-toast';
import { FullScreenSpinner } from '@a-type/ui/components/spinner';
import { ParticleLayer } from '@a-type/ui/components/particles';
import { useVisualViewportOffset } from '@a-type/ui/hooks';
import { Explainer } from './components/onboarding/Explainer.jsx';

const graphqlClient = createGraphQLClient();

Expand All @@ -37,6 +38,7 @@ export function App() {
position="bottom-center"
containerClassName="mb-10 sm:mb-0"
/>
<Explainer />
</TooltipProvider>
</LofiProvider>
</Provider>
Expand Down
20 changes: 16 additions & 4 deletions apps/trip-tick/web/src/components/lists/AddListButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { hooks } from '@/store.js';
import { Button } from '@a-type/ui/components/button';
import { useMe } from '@biscuits/client';
import { useNavigate } from '@verdant-web/react-router';
import { ReactNode } from 'react';

Expand All @@ -15,15 +16,26 @@ export function AddListButton({
}: AddListButtonProps) {
const client = hooks.useClient();
const navigate = useNavigate();
const { data: me } = useMe();
const allLists = hooks.useAllLists();
const hasLists = allLists.length > 0;

return (
<Button
color="primary"
onClick={async () => {
const list = await client.lists.put({
name: 'New list',
});
navigate(`/lists/${list.get('id')}`);
if (hasLists) {
const list = await client.lists.put({
name: 'New list',
});
navigate(`/lists/${list.get('id')}`);
} else {
const listName = me ? me.me.name : `My stuff`;
const list = await client.lists.put({
name: listName,
});
navigate(`/lists/${list.get('id')}`);
}
}}
className={className}
{...rest}
Expand Down
58 changes: 58 additions & 0 deletions apps/trip-tick/web/src/components/onboarding/Explainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { H1, P } from '@a-type/ui/components/typography';
import { Explainer as ExplainerBase } from '@biscuits/client';

import screen1 from './screen1.png';
import screen2 from './screen2.png';
import screen3 from './screen3.png';

export function Explainer() {
return <ExplainerBase stages={stages} />;
}

const stages = [
<>
<H1>How to use Trip Tick</H1>
<P>
Trip Tick has two parts: <b>Trips</b> and <b>Lists</b>.
</P>
<img src={screen1} alt="Trip Tick screenshot" className="w-full" />
<P>
Here's what a trip looks like with some lists added ("Grant" and
"Pashka").
</P>
</>,
<>
<H1>Lists</H1>
<P>
Lists are collections of items that you can add to a trip. You make a list
once, then use it each time you plan a new trip.
</P>
<img src={screen2} alt="Trip Tick screenshot" className="w-full" />
<P>
Your lists let Trip Tick do the math for you. Configure item quantities
like "1 per 3 days" and the app will tell you how many to pack based on
your trip length.
</P>
<P>
You can create a list for anything you want to keep track of while
packing, like a packing list or a to-do list.
</P>
</>,
<>
<H1>Trips</H1>
<P>
Trips are collections of lists that you can use to keep track of your
packing progress.
</P>
<img src={screen3} alt="Trip Tick screenshot" className="w-full" />
<P>
Each time you plan a new trip, you choose a date range and select which
lists you need to pack.
</P>
<P>
When it's time to start packing, you check off items from those lists as
you add them to your bag.
</P>
<P>That's it. Save travels!</P>
</>,
];
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
147 changes: 77 additions & 70 deletions apps/trip-tick/web/src/components/trips/TripItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export function ExtraItem({
onDescriptionChanged={(value) => item.set('description', value)}
onQuantityChanged={(value) => item.set('quantity', value)}
onDelete={onDelete}
subline="Added for this trip"
/>
);
}
Expand Down Expand Up @@ -155,84 +156,90 @@ function ChecklistItem({

return (
<div className="w-full p-2 flex flex-col gap-2">
<div className="w-full flex flex-row items-center gap-2 flex-wrap">
<div className="row w-full">
<CheckboxRoot
checked={completed}
onCheckedChange={mainOnChecked}
className="w-32px h-32px rounded-full touch-none flex items-center justify-center text-black"
>
<Icon name={completed ? 'check' : 'plus'} />
<Icon name="check" />
</CheckboxRoot>
{onDescriptionChanged && editing ? (
<LiveUpdateTextField
value={description}
onChange={onDescriptionChanged}
placeholder="What is it?"
className="flex-1 min-w-50%"
/>
) : (
<label className="font-bold select-none">{description}</label>
)}
{!!onQuantityChanged && editing && (
<NumberStepper
value={computedQuantity}
onChange={onQuantityChanged}
className="mr-auto"
/>
)}
{editing && onDelete && (
<Button size="icon" color="ghostDestructive" onClick={onDelete}>
<Icon name="x" />
</Button>
)}
{canEdit && (
<Button
size="icon"
color={editing ? 'default' : 'ghost'}
onClick={() => setEditing((v) => !v)}
<div className="col items-start flex-1">
<div className="w-full flex flex-row items-center gap-2 flex-wrap">
{onDescriptionChanged && editing ? (
<LiveUpdateTextField
value={description}
onChange={onDescriptionChanged}
placeholder="What is it?"
className="flex-1 min-w-50%"
/>
) : (
<label className="font-bold select-none">{description}</label>
)}
{!!onQuantityChanged && editing && (
<NumberStepper
value={computedQuantity}
onChange={onQuantityChanged}
className="mr-auto"
/>
)}
{editing && onDelete && (
<Button size="icon" color="ghostDestructive" onClick={onDelete}>
<Icon name="x" />
</Button>
)}
{canEdit && (
<Button
size="icon"
color={editing ? 'default' : 'ghost'}
onClick={() => setEditing((v) => !v)}
>
<Icon name={editing ? 'check' : 'pencil'} />
</Button>
)}
</div>
<SliderRoot
className="flex-1"
value={[completedQuantity]}
min={0}
max={computedQuantity}
onValueChange={([v]) => onCompletionChanged(v)}
style={{
// Fix overflow clipping in Safari
// https://gist.github.com/domske/b66047671c780a238b51c51ffde8d3a0
transform: 'translateZ(0)',
}}
ref={barRef}
>
<Icon name={editing ? 'check' : 'pencil'} />
</Button>
)}
<SliderTrack>
<SliderRange
className="transition-all"
data-color={completed ? 'default' : 'primary'}
/>
{new Array(computedQuantity - 1).fill(0).map((_, i) => (
<div
key={i}
className="w-1px h-full bg-gray-6 absolute top-0 left-0"
style={{
left: `${(100 / computedQuantity) * (i + 1)}%`,
}}
/>
))}
</SliderTrack>
<SliderThumb
data-color={completed ? 'default' : 'primary'}
className={classNames(
'transition-all',
completed && 'bg-accent ring-black ring-1 text-white',
'flex items-center justify-center',
completedQuantity === 0 && 'opacity-0',
)}
>
{completed && <Icon name="check" />}
</SliderThumb>
</SliderRoot>
</div>
</div>
<SliderRoot
value={[completedQuantity]}
min={0}
max={computedQuantity}
onValueChange={([v]) => onCompletionChanged(v)}
style={{
// Fix overflow clipping in Safari
// https://gist.github.com/domske/b66047671c780a238b51c51ffde8d3a0
transform: 'translateZ(0)',
}}
ref={barRef}
>
<SliderTrack>
<SliderRange
className="transition-all"
data-color={completed ? 'default' : 'primary'}
/>
{new Array(computedQuantity - 1).fill(0).map((_, i) => (
<div
key={i}
className="w-1px h-full bg-gray-6 absolute top-0 left-0"
style={{
left: `${(100 / computedQuantity) * (i + 1)}%`,
}}
/>
))}
</SliderTrack>
<SliderThumb
data-color={completed ? 'default' : 'primary'}
className={classNames(
'transition-all',
completed && 'bg-accent ring-black ring-1 text-white',
'flex items-center justify-center',
)}
>
{completed && <Icon name="check" />}
</SliderThumb>
</SliderRoot>
<div className="flex flex-row justify-between gap-2 items-center text-xs text-gray-7">
{subline && <div className="italic">{subline}</div>}
<span className="ml-auto">
Expand Down
7 changes: 7 additions & 0 deletions apps/trip-tick/web/src/components/trips/TripView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ function TripViewChecklists({
</TabsContent>
);
})}
{startedWithNoLists && !mappedLists.length && (
<div className="w-full p-4 text-gray-7 italic">
<span className="[font-style:normal]">💡</span> Add lists to this trip
for everything you want to pack. Once you start packing, check off
items as you go.
</div>
)}
</TabsRoot>
);
}
Expand Down
56 changes: 56 additions & 0 deletions packages/client/src/components/Explainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Button } from '@a-type/ui/components/button';
import {
Dialog,
DialogContent,
DialogActions,
DialogClose,
} from '@a-type/ui/components/dialog';
import { useLocalStorage } from '../hooks/useStorage.js';
import { ReactNode, useState } from 'react';

export interface ExplainerProps {
stages: ReactNode[];
}

export function Explainer({ stages }: ExplainerProps) {
const [explainerDismissed, setExplainerDismissed] = useLocalStorage(
'explainerDismissed',
false,
);
const [stage, setStage] = useState(0);
return (
<Dialog
open={!explainerDismissed}
onOpenChange={(open) => {
if (!open) {
setExplainerDismissed(true);
}
}}
>
<DialogContent
outerClassName="h-screen max-h-none sm:max-h-[80vh] overflow-y-auto"
className="h-screen sm:h-auto"
>
<div className="col gap-4 flex-1 items-start">{stages[stage]}</div>
<DialogActions>
<DialogClose asChild>
<Button>Skip</Button>
</DialogClose>
<Button
className="ml-auto"
color="primary"
onClick={() => {
if (stage === stages.length - 1) {
setExplainerDismissed(true);
} else {
setStage(stage + 1);
}
}}
>
{stage === stages.length - 1 ? 'Got it!' : 'Next'}
</Button>
</DialogActions>
</DialogContent>
</Dialog>
);
}
1 change: 1 addition & 0 deletions packages/client/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export * from './onboarding.js';
export * from './TosPrompt.js';
export * from './Essentials.js';
export * from './ReloadButton.js';
export * from './Explainer.js';

0 comments on commit 38e174e

Please sign in to comment.