Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .changeset/good-pants-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"strapi-plugin-webtools": patch
---

Feature/try webtools inscentives
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,5 @@ jobs:
with:
flags: unit
verbose: true
fail_ci_if_error: true
fail_ci_if_error: false

33 changes: 33 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ Builds all packages using Turborepo's caching and dependency graph.

## Testing

### Node version

Always activate Node 20 before running yarn commands:

```bash
nvm use # reads .nvmrc (Node 20)
```

The repo uses **Yarn 4.11.0** via `yarnPath` in `.yarnrc.yml`. Running `yarn` anywhere inside the repo automatically uses this version — even in subdirectories like `playground/`.

### Unit Tests

```bash
Expand All @@ -69,6 +79,8 @@ Unit tests are located in `__tests__` directories:

Uses Jest with ts-jest preset. Test files: `*.test.ts` or `*.test.js`

`playground/.env` is **force-committed** to git with placeholder values (`tobemodified`) so CI can boot Strapi. Do not add real secrets to it — use your local `.env` override for `WEBTOOLS_LICENSE_KEY` and similar.

### Integration Tests

```bash
Expand Down Expand Up @@ -184,6 +196,10 @@ Addons are Strapi plugins with a special flag in their `package.json`:

**Discovery**: Core scans `enabledPlugins` at runtime for this flag

**Addon detection in admin UI**: Always match on `addon.info.name` (the Strapi plugin name, e.g. `webtools-addon-redirects`), **not** on UI labels which are translatable. Strip the npm scope before comparing: `@pluginpal/webtools-addon-redirects` → `webtools-addon-redirects`.

**Pro addons** (not open-source) are defined in `admin/constants/pro-addons.ts`. When not installed, they appear in the sidebar as locked items (`LockedAddonMenuItem`) that open a `TrialModal`. The `packageName` field in `ProAddon` is the full scoped npm name.

**Integration**: Addons inject components via named zones:
- `webtoolsRouter`: Adds routes to main navigation
- `webtoolsSidePanel`: Adds components to content editor sidebar
Expand Down Expand Up @@ -296,6 +312,23 @@ See `packages/addons/sitemap` as reference:
```
4. Optionally extend content types in server `bootstrap()`

## ESLint rules to watch

- **`max-len`**: 100 character limit. Long inline comments will fail.
- **`no-confusing-arrow` + `implicit-arrow-linebreak`**: Arrow functions with ternaries must use a block body:
```typescript
// ✗ fails linting
const fn = (x: string) =>
x.includes('/') ? x.split('/')[1] : x;

// ✓ correct
const fn = (x: string) => {
if (x.includes('/')) return x.split('/')[1];
return x;
};
```
- **`@typescript-eslint/no-unnecessary-type-assertion`**: Remove casts that TypeScript already infers.

## Release Process

This project uses Changesets for version management:
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/subcommands/license-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function licenseSetup() {
`${chalk.bold('🚀 Get your free trial license!')}\n`,
);
console.log('You can start your free trial by visiting the following link:');
console.log(chalk.underline('https://buy.polar.sh/polar_cl_nOL8JflMooiHSJe6Fsf5CZhoEbMDvaBi9Q8HP2CWYm9'));
console.log(chalk.underline('https://www.pluginpal.io/plugin/webtools'));
console.log('\n✨ Enjoy 7 days of access to the Essential plan completely free!');
console.log('💡 Remember: You can cancel within the 7 days to ensure your trial remains free.\n');

Expand Down
52 changes: 52 additions & 0 deletions packages/core/admin/components/LockedAddonMenuItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useState } from 'react';
import { useIntl } from 'react-intl';
import {
SubNavLink,
Tooltip,
} from '@strapi/design-system';
import { Lock } from '@strapi/icons';
import { LockedAddonMenuItemProps } from '../../types/pro-addons';
import TrialModal from '../TrialModal';

const LockedAddonMenuItem: React.FC<LockedAddonMenuItemProps> = ({ addon }) => {
const { formatMessage } = useIntl();
const [isModalOpen, setIsModalOpen] = useState(false);

const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
setIsModalOpen(true);
};

return (
<>
<Tooltip
description={formatMessage({
id: 'webtools.sidebar.locked_addon.tooltip',
defaultMessage: 'Start free trial to unlock',
})}
>
<SubNavLink
onClick={handleClick}
style={{
cursor: 'pointer',
opacity: 0.5,
pointerEvents: 'auto',
}}
>
<span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Lock width="12px" height="12px" />
{addon.name}
</span>
</SubNavLink>
</Tooltip>

<TrialModal
addon={addon}
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
/>
</>
);
};

export default LockedAddonMenuItem;
71 changes: 71 additions & 0 deletions packages/core/admin/components/TrialCallToAction/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { useIntl } from 'react-intl';
import {
Box,
Flex,
Typography,
Button,
} from '@strapi/design-system';
import { TRIAL_URL } from '../../constants/pro-addons';

interface TrialCallToActionProps {
variant?: 'banner' | 'card' | 'inline';
}

const TrialCallToAction: React.FC<TrialCallToActionProps> = ({ variant = 'card' }) => {
const { formatMessage } = useIntl();

const content = (
<Flex direction="column" alignItems="center" gap={2}>
<Typography variant="delta" textAlign="center">
{formatMessage({
id: 'webtools.overview.trial_cta.title_short',
defaultMessage: 'Ready to unlock Pro features?',
})}
</Typography>

<Typography variant="omega" textColor="neutral600" textAlign="center">
{formatMessage({
id: 'webtools.overview.trial_cta.subtitle',
defaultMessage: 'Start your free 7-day trial - includes Redirects & Links addons',
})}
</Typography>

<Box marginTop={3}>
<Button
variant="default"
tag="a"
href={TRIAL_URL}
target="_blank"
rel="noopener noreferrer"
>
{formatMessage({
id: 'webtools.overview.trial_cta.button_short',
defaultMessage: 'Start Free Trial',
})}
</Button>
</Box>
</Flex>
);

if (variant === 'inline') {
return content;
}

return (
<Box
hasRadius
background="neutral0"
shadow="tableShadow"
paddingTop={8}
paddingBottom={8}
paddingRight={8}
paddingLeft={8}
style={variant === 'banner' ? { width: '100%' } : undefined}
>
{content}
</Box>
);
};

export default TrialCallToAction;
Loading
Loading