Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { GivebackActionCatalog } from '@dailydotdev/shared/src/features/giveback/components/GivebackActionCatalog';
import { mockActions, withGiveback } from './giveback.mocks';

// The "Take action" grid: paid growth actions with category filter chips and a
// "Show more" expand, plus the voluntary "love" actions. Each card opens the
// submission modal. Reads actions + categories from the actions query.
const meta: Meta<typeof GivebackActionCatalog> = {
title: 'Features/Giveback/Action catalog',
component: GivebackActionCatalog,
parameters: {
layout: 'padded',
docs: {
description: {
component:
'Filter by category, open a card to launch the submission modal. Shows the full catalog and an empty state.',
},
},
},
};

export default meta;

type Story = StoryObj<typeof GivebackActionCatalog>;

export const Default: Story = {
decorators: [withGiveback()],
};

export const SingleCategory: Story = {
parameters: {
docs: { description: { story: 'Only content actions — no category chips.' } },
},
decorators: [
withGiveback({
actions: mockActions().filter((a) => a.categoryId === 'cat-content'),
categories: [{ id: 'cat-content', title: 'Content' }],
}),
],
};

export const Empty: Story = {
decorators: [withGiveback({ actions: [], categories: [] })],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { GivebackActionSubmissionModal } from '@dailydotdev/shared/src/features/giveback/components/GivebackActionSubmissionModal';
import type { ContributionAction } from '@dailydotdev/shared/src/features/giveback/types';
import { withGiveback } from './giveback.mocks';

// The proof-submission modal opened from an action card. The form adapts to the
// action's `evidence`: a link field, a screenshot upload, and/or a note. Love
// actions skip the reward and just say thanks.
const meta: Meta<typeof GivebackActionSubmissionModal> = {
title: 'Features/Giveback/Submission modal',
component: GivebackActionSubmissionModal,
args: { onClose: () => undefined },
parameters: {
layout: 'fullscreen',
docs: {
description: {
component:
'The pop-up that collects proof for an action. Variants show the link-only, screenshot, full (link + screenshot + note), and the love-action (no reward) layouts.',
},
},
},
decorators: [withGiveback()],
};

export default meta;

type Story = StoryObj<typeof GivebackActionSubmissionModal>;

const makeAction = (
overrides: Partial<ContributionAction>,
): ContributionAction => ({
id: 'a-modal',
categoryId: 'cat-content',
title: 'Post about daily.dev on X',
description: 'A quick post about what you like helps more developers find us.',
points: 120,
evidence: { url: { required: true } },
metadata: {
platform: 'x',
instructions:
'Write a short post about what you like in daily.dev.\nInclude a link to daily.dev so people can find it.\nCopy the link to your post and paste it below.',
externalUrl: 'https://x.com/compose/post',
isLoveAction: false,
},
cooldownSeconds: null,
maxPerUser: null,
userCooldownEndsAt: null,
userCompletions: 0,
latestUserSubmission: null,
...overrides,
});

export const LinkOnly: Story = {
parameters: {
docs: {
description: {
story:
'Branded action: platform logo, the ask, the reward, a numbered how-to, and a "Go to X" button — then the proof link field.',
},
},
},
args: { action: makeAction({}) },
};

export const Screenshot: Story = {
args: {
action: makeAction({
title: 'Host a daily.dev meetup',
description: 'Bring developers together in person around daily.dev.',
points: 250,
evidence: { screenshot: { required: true } },
metadata: {
platform: 'event',
instructions: 'Upload a photo from the meetup.',
externalUrl: null,
isLoveAction: false,
},
}),
},
};

export const FullProof: Story = {
args: {
action: makeAction({
title: 'Speak about daily.dev at an event',
points: 200,
evidence: {
url: { required: true },
screenshot: { required: false },
note: { required: false },
},
}),
},
};

export const LoveAction: Story = {
parameters: {
docs: { description: { story: 'A voluntary thank-you — no reward attached.' } },
},
args: {
action: makeAction({
title: 'Leave us a kind word',
points: 0,
evidence: { note: { required: true } },
metadata: {
platform: null,
instructions: null,
externalUrl: null,
isLoveAction: true,
},
}),
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { GivebackFaq } from '@dailydotdev/shared/src/features/giveback/components/GivebackFaq';
import { withGiveback } from './giveback.mocks';

// The building blocks of the FAQ tab. The campaign's "why" headline now lives in
// the page hero, so this is the FAQ on its own.
const meta: Meta = {
title: 'Features/Giveback/Campaign pieces',
parameters: { layout: 'padded' },
decorators: [withGiveback({ selectedCauseIds: ['c-oss', 'c-access', 'c-docs'] })],
};

export default meta;

type Story = StoryObj;

export const Faq: Story = {
render: () => <GivebackFaq />,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { GivebackCauseCard } from '@dailydotdev/shared/src/features/giveback/components/GivebackCauseCard';
import { mockCauses } from './giveback.mocks';

const cause = mockCauses()[0];

// The rich, selectable cause card used in the onboarding picker and the "more
// causes to explore" grid: emblem, name, category, full description (clamped),
// a "learn more" link to the cause site, and the select tick.
const meta: Meta<typeof GivebackCauseCard> = {
title: 'Features/Giveback/Cause card',
component: GivebackCauseCard,
args: { cause, index: 0, onToggle: () => undefined },
parameters: { layout: 'padded' },
decorators: [
(Story) => (
<div className="grid max-w-[28rem] gap-3 bg-background-default p-4">
<Story />
</div>
),
],
};

export default meta;

type Story = StoryObj<typeof GivebackCauseCard>;

export const Unselected: Story = { args: { selected: false } };

export const Selected: Story = { args: { selected: true } };

export const Interactive: Story = {
parameters: {
docs: { description: { story: 'Click the card to toggle selection.' } },
},
render: (args) => {
const [selected, setSelected] = useState(false);
return (
<GivebackCauseCard
{...args}
selected={selected}
onToggle={() => setSelected((value) => !value)}
/>
);
},
};

export const WithoutLinkOrDescription: Story = {
args: {
selected: false,
cause: { ...cause, url: null, description: null },
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { GivebackCauseSelection } from '@dailydotdev/shared/src/features/giveback/components/GivebackCauseSelection';
import { mockCauses, withGiveback } from './giveback.mocks';

// The cause picker grid + category filter chips. Prop-driven: pass the causes,
// the selected ids set, and a toggle handler. These stories wire an interactive
// selection so you can click cards and filter.
const meta: Meta<typeof GivebackCauseSelection> = {
title: 'Features/Giveback/Cause selection',
component: GivebackCauseSelection,
parameters: {
layout: 'padded',
docs: {
description: {
component:
'Interactive: click cards to select, use the category chips to filter. Covers the loaded grid, a pre-selected state, the loading skeleton, and the empty state.',
},
},
},
decorators: [withGiveback()],
};

export default meta;

type Story = StoryObj<typeof GivebackCauseSelection>;

const Interactive = ({
preset = [],
isLoading = false,
empty = false,
}: {
preset?: string[];
isLoading?: boolean;
empty?: boolean;
}) => {
const [selectedIds, setSelectedIds] = useState<Set<string>>(
() => new Set(preset),
);
return (
<div className="mx-auto max-w-4xl">
<GivebackCauseSelection
causes={empty ? [] : mockCauses()}
isLoading={isLoading}
selectedIds={selectedIds}
onToggle={(id) =>
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
})
}
/>
</div>
);
};

export const Default: Story = { render: () => <Interactive /> };

export const WithPreselected: Story = {
render: () => <Interactive preset={['c-oss', 'c-access', 'c-docs']} />,
};

export const Loading: Story = { render: () => <Interactive isLoading /> };

export const Empty: Story = { render: () => <Interactive empty /> };
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { GivebackCausesPanel } from '@dailydotdev/shared/src/features/giveback/components/GivebackCausesPanel';
import { withGiveback } from './giveback.mocks';

// The "Causes" management tab: every cause as a pickable card, the ones you
// already back up top, everything else below. Toggle cards to see the "Save
// changes" bar appear (it only shows when the working set differs from what's
// saved).
const meta: Meta<typeof GivebackCausesPanel> = {
title: 'Features/Giveback/Causes tab',
component: GivebackCausesPanel,
parameters: { layout: 'padded' },
};

export default meta;

type Story = StoryObj<typeof GivebackCausesPanel>;

export const WithSelection: Story = {
decorators: [
withGiveback({ selectedCauseIds: ['c-oss', 'c-access', 'c-docs'] }),
],
};

export const NothingPicked: Story = {
parameters: {
docs: {
description: {
story: 'Empty "Your causes" state, with everything available below.',
},
},
},
decorators: [withGiveback({ selectedCauseIds: [] })],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { GivebackContributionSummary } from '@dailydotdev/shared/src/features/giveback/components/GivebackContributionSummary';
import { mockStatus, withGiveback } from './giveback.mocks';

// The personal recap above the action catalog: square avatar + level badge,
// amount unlocked for your causes, actions taken, and the next reward. Reads
// your points, reward tiers and completed actions.
const meta: Meta<typeof GivebackContributionSummary> = {
title: 'Features/Giveback/Contribution summary',
component: GivebackContributionSummary,
parameters: {
layout: 'padded',
docs: {
description: {
component:
'Personal progress recap. The "to go" figure is green; the avatar is a rounded square with a level badge. Shown at a few point levels.',
},
},
},
};

export default meta;

type Story = StoryObj<typeof GivebackContributionSummary>;

export const Default: Story = {
decorators: [withGiveback({ status: mockStatus({ userPoints: 320 }) })],
};

export const FreshContributor: Story = {
decorators: [withGiveback({ status: mockStatus({ userPoints: 0 }) })],
};

export const AllRewardsUnlocked: Story = {
decorators: [withGiveback({ status: mockStatus({ userPoints: 2500 }) })],
};
Loading
Loading