Skip to content

Commit

Permalink
feat(sdk): enable ts strict mode and next.js example app (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
DSergiu authored Apr 22, 2024
1 parent 151a7bb commit f619b8e
Show file tree
Hide file tree
Showing 20 changed files with 650 additions and 190 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:

- run: pnpm build

- run: pnpm typecheck

- run: pnpm test

- run: cp README.md packages/vanilla-hcaptcha
Expand Down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mainstream web frameworks such as: React, Preact, Vue.js, Angular, Stencil.js, e
* [Preact](#reactjs-and-preact)
* [Angular 2+](#angular)
* [Angular.JS](#angularjs)
* [Next.JS](#nextjs)
* [Vanilla](#vanillajs)
* You can find more examples in the `<root>/examples/cdn` directory.

Expand Down Expand Up @@ -177,6 +178,54 @@ mainstream web frameworks such as: React, Preact, Vue.js, Angular, Stencil.js, e
</html>
```

### Next.JS

> Example: display normal size hCaptcha with dark theme.

1. Add `h-captcha` web component type by extending `JSX.IntrinsicElements` in `*.d.ts`.
```ts
import * as React from 'react';
declare global {
declare namespace JSX {
interface IntrinsicElements {
'h-captcha': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
[k: string]: unknown;
}, HTMLElement>;
}
}
}
```

2. Integrate in your next.js page.
```js
export default function HomeComponent() {
const sitekey = '781559eb-513a-4bae-8d29-d4af340e3624';
const captchaRef = createRef<HTMLElement>();
useEffect(() => {
import('@hcaptcha/vanilla-hcaptcha');
if (captchaRef.current) {
captchaRef.current.addEventListener('verified', (e: Event) => {
console.log('hCaptcha event: verified', { token: e.token });
});
}
}, []);
return (
<main>
<h-captcha
ref={captchaRef}
sitekey={sitekey}
size="normal"
theme="dark"
></h-captcha>
</main>
);
}
```

### Vanilla.JS

> Example: display normal size hCaptcha accessible by keyboard (see [tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex)).
Expand Down
3 changes: 2 additions & 1 deletion examples/cdn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"test": "echo Examples have no tests."
},
"dependencies": {
"@hcaptcha/vanilla-hcaptcha": "link:../../packages/vanilla-hcaptcha"
"@hcaptcha/vanilla-hcaptcha": "link:../../packages/vanilla-hcaptcha",
"serve": "^13.0.2"
}
}
36 changes: 36 additions & 0 deletions examples/nextjs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
40 changes: 40 additions & 0 deletions examples/nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
11 changes: 11 additions & 0 deletions examples/nextjs/jsx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';

declare global {
declare namespace JSX {
interface IntrinsicElements {
'h-captcha': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
[k: string]: unknown;
}, HTMLElement>;
}
}
}
6 changes: 6 additions & 0 deletions examples/nextjs/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};

export default nextConfig;
23 changes: 23 additions & 0 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "nextjs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@hcaptcha/vanilla-hcaptcha": "link:../../packages/vanilla-hcaptcha",
"next": "14.2.1",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"typescript": "^5"
}
}
Binary file added examples/nextjs/public/favicon.ico
Binary file not shown.
48 changes: 48 additions & 0 deletions examples/nextjs/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState, createRef, useEffect } from 'react';

export default function Home() {
const [token, setToken] = useState('');
const sitekey = '781559eb-513a-4bae-8d29-d4af340e3624';
const jsapi = 'https://js.hcaptcha.com/1/api.js';
const captchaRef = createRef<HTMLElement>();

useEffect(() => {
console.warn('next.js in dev mode will call useEffect twice. Link: https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development');

import('@hcaptcha/vanilla-hcaptcha');

if (captchaRef.current) {
captchaRef.current.addEventListener('verified', (e: Event) => {
// @ts-ignore
const token = e.token;
console.log('hCaptcha event: verified', { token });
setToken(token);
});

captchaRef.current.addEventListener('closed', () => {
console.log('hCaptcha event: closed');
});

captchaRef.current.addEventListener('error', (e: Event) => {
console.log('hCaptcha event: error', e);
});
}
}, []);

return (
<main>
<div>hCaptcha size: normal, theme: dark</div>
<h-captcha
ref={captchaRef}
sitekey={sitekey}
jsapi={jsapi}
host="example.com"
size="normal"
theme="dark"
></h-captcha>

<div>Token:</div>
<div>{token}</div>
</main>
);
}
21 changes: 21 additions & 0 deletions examples/nextjs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "jsx.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@
"license": "MIT",
"scripts": {
"setup": "pnpm install --frozen-lockfile --prefer-offline",
"typecheck": "pnpm -r --stream run typecheck",
"dev": "pnpm -r --stream run dev",
"build": "pnpm -r --stream run build",
"test": "pnpm --r -stream run test"
},
"devDependencies": {
"serve": "^13.0.2"
}
}
3 changes: 1 addition & 2 deletions packages/vanilla-hcaptcha/__tests__/unit/hcaptcha.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ describe('hCaptcha Vanilla Web Component', () => {
it('should proxy "setData" method', () => {
const mockRqData = 'invalid-site-key';
hCaptchaEl.setData(mockRqData);
expect(window.hcaptcha.setData).toHaveBeenNthCalledWith(1, mockCaptchaId, { rqdata: null });
expect(window.hcaptcha.setData).toHaveBeenNthCalledWith(2, mockCaptchaId, { rqdata: mockRqData });
expect(window.hcaptcha.setData).toHaveBeenNthCalledWith(1, mockCaptchaId, { rqdata: mockRqData });
});

it('should automatically render the checkbox', (done) => {
Expand Down
5 changes: 4 additions & 1 deletion packages/vanilla-hcaptcha/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"demo.html"
],
"scripts": {
"typecheck": "tsc -p ./tsconfig.json",
"dev": "rollup -c -w",
"build": "rollup -c",
"test": "jest"
Expand Down Expand Up @@ -55,6 +56,8 @@
"vue.js",
"preact",
"react",
"react.js"
"react.js",
"nextjs",
"next.js"
]
}
18 changes: 5 additions & 13 deletions packages/vanilla-hcaptcha/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,22 @@ import { terser } from "rollup-plugin-terser";
import filesize from "rollup-plugin-filesize";
import typescript from '@rollup/plugin-typescript';

export default [{
export default {
input: './src/index.ts',
output: {
output: [{
file: './dist/index.min.js',
format: 'iife',
name: 'bundle',
sourcemap: true
},
plugins: [
typescript({ tsconfig: './tsconfig.json' }),
terser(),
filesize()
]
}, {
input: './src/index.ts',
output: {
}, {
file: './dist/index.min.mjs',
format: 'es',
name: 'bundle',
sourcemap: true
},
}],
plugins: [
typescript({ tsconfig: './tsconfig.json' }),
terser(),
filesize()
]
}];
};
6 changes: 3 additions & 3 deletions packages/vanilla-hcaptcha/src/api-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export function loadJsApiIfNotAlready(config: VanillaHCaptchaJsApiConfig): Promi
window._hCaptchaOnLoadPromise = new Promise((resolve, reject) => {
resolveFn = resolve;
rejectFn = reject;
window._hCaptchaOnLoad = resolveFn;
});
window._hCaptchaOnLoad = resolveFn;
const scriptSrc = getScriptSrc(config);
const script = document.createElement('script');
script.src = scriptSrc;
Expand All @@ -53,7 +53,7 @@ function getScriptSrc(config: VanillaHCaptchaJsApiConfig) {
let scriptSrc = config.jsapi;
scriptSrc = addQueryParamIfDefined(scriptSrc, 'render', 'explicit');
scriptSrc = addQueryParamIfDefined(scriptSrc, 'onload', '_hCaptchaOnLoad');
scriptSrc = addQueryParamIfDefined(scriptSrc, 'recaptchacompat', config.recaptchacompat === 'false' ? 'off' : null);
scriptSrc = addQueryParamIfDefined(scriptSrc, 'recaptchacompat', config.recaptchacompat === 'false' ? 'off' : undefined);
scriptSrc = addQueryParamIfDefined(scriptSrc, 'host', config.host);
scriptSrc = addQueryParamIfDefined(scriptSrc, 'hl', config.hl);
scriptSrc = addQueryParamIfDefined(scriptSrc, 'sentry', config.sentry === 'false' ? 'false' : 'true');
Expand All @@ -64,7 +64,7 @@ function getScriptSrc(config: VanillaHCaptchaJsApiConfig) {
return scriptSrc;
}

function addQueryParamIfDefined(url: string, queryName: string, queryValue: string) {
function addQueryParamIfDefined(url: string, queryName: string, queryValue: string | undefined) {
if (queryValue !== undefined && queryValue !== null) {
const link = url.includes('?') ? '&' : '?';
return url + link + queryName + '=' + encodeURIComponent(queryValue);
Expand Down
Loading

0 comments on commit f619b8e

Please sign in to comment.