@undp/create-app 0.2.2 โ†’ 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ๐Ÿ—๏ธ UNDP Create App CLI ![npm](https://img.shields.io/npm/v/@undp/create-app)
2
2
 
3
- **`@undp/create-app`** is UNDPโ€™s official **project scaffolding tool** for quickly bootstrapping frontend applications using **React**, and **TypeScript** โ€” complete with sensible defaults for ESLint, Prettier, and UNDPโ€™s Design System.
3
+ **`@undp/create-app`** is UNDPโ€™s official **project scaffolding tool** for quickly bootstrapping frontend applications using **React**, and **TypeScript** โ€” complete with sensible defaults for ESLint, Prettier.
4
4
 
5
5
  [NPM Package](https://www.npmjs.com/package/@undp/create-app)
6
6
 
@@ -8,14 +8,15 @@
8
8
 
9
9
  ## ๐Ÿš€ Features
10
10
 
11
- - โšก **React + TypeScript** setup out of the box
12
- - ๐ŸŽจ Includes **Tailwind CSS** for styling
13
- - ๐Ÿง  Includes **Zustand** for state management
14
- - ๐Ÿš€ **Vite support** for fast SPA development, including optional **routing** and **data fetching** with **TanStack** libraries
11
+ - โš›๏ธ **React + TypeScript** setup out of the box
12
+ - ๐Ÿ–Œ๏ธ Includes **Tailwind CSS** for styling
13
+ - ๐Ÿ—‚๏ธ Includes **Zustand** for state management
14
+ - ๐Ÿš€ **Vite support** for fast SPA development, including optional **routing** with **Tanstack router**
15
15
  - ๐ŸŒ **Next.js support** for full-stack apps, including optional **authentication** with **Better Auth**
16
- - ๐Ÿงฑ Preconfigured for **UNDPโ€™s Design System**
16
+ - ๐ŸŽจ Preconfigured for **UNDPโ€™s Design System**
17
+ - โšก๏ธ Optional integration with **Tanstack query** for data fetching
17
18
  - ๐Ÿ“Š Optional integration with **@undp/data-viz** for interactive data visualization
18
- - ๐Ÿงผ **ESLint** + **Prettier** preconfigured for consistent code style
19
+ - โœจ **ESLint** + **Prettier** preconfigured for consistent code style
19
20
 
20
21
  ---
21
22
 
@@ -26,9 +27,7 @@ All setups include **Tailwind CSS** for styling and **Zustand** for state manage
26
27
  | Framework | Variants | Description |
27
28
  |------------|-----------|-------------|
28
29
  | **Vite** | Basic | Minimal React + TypeScript setup |
29
- | **Vite** | + Query | Adds TanStack Query for data fetching |
30
30
  | **Vite** | + Router | Adds TanStack Router for routing |
31
- | **Vite** | + Router + Query | Full-featured Vite setup |
32
31
  | **Next.js** | Basic | SSR and file-based routing |
33
32
  | **Next.js** | + Auth | Includes authentication via Better Auth |
34
33
 
@@ -1,8 +1,10 @@
1
- export function generateLayoutForNext(dataViz, projectName) {
1
+ export function generateLayoutForNext(dataViz, projectName, query) {
2
2
  return `import type { Metadata } from 'next';
3
+ import { ReactNode } from 'react';
3
4
 
4
5
  import HeaderEl from '@/components/Header';
5
- import FooterEl from '@/components/Footer';
6
+ import FooterEl from '@/components/Footer';${query ? `
7
+ import TanStackQueryProvider from '@/integration/tanstack-query';`:''}
6
8
 
7
9
  import './globals.css';
8
10
  import '@undp/design-system-react/style.css';${dataViz ? `
@@ -16,14 +18,17 @@ export const metadata: Metadata = {
16
18
  export default function RootLayout({
17
19
  children,
18
20
  }: Readonly<{
19
- children: React.ReactNode;
21
+ children: ReactNode;
20
22
  }>) {
21
23
  return (
22
24
  <html lang='en'>
23
25
  <body className='flex flex-col gap-0 min-h-screen'>
24
26
  <HeaderEl />
25
- <main className='grow-1 flex flex-col justify-center'>
26
- <div className='flex flex-col justify-center'>{children}</div>
27
+ <main className='grow-1 flex flex-col justify-center'>${query ? `
28
+ <TanStackQueryProvider>
29
+ <div className='flex flex-col justify-center'>{children}</div>
30
+ </TanStackQueryProvider>` : `
31
+ <div className='flex flex-col justify-center'>{children}</div>`}
27
32
  </main>
28
33
  <FooterEl />
29
34
  </body>
@@ -38,16 +38,9 @@ export function generatePackageJson(config) {
38
38
  };
39
39
 
40
40
  switch (config.framework) {
41
- case 'vite-query':
42
- dependencies['@tanstack/react-query'] = '^5.90.7';
43
- break;
44
41
  case 'vite-router':
45
42
  dependencies['@tanstack/react-router'] = '^1.135.0';
46
43
  break;
47
- case 'vite-full':
48
- dependencies['@tanstack/react-query'] = '^5.90.7';
49
- dependencies['@tanstack/react-router'] = '^1.135.0';
50
- break;
51
44
  case 'next-basic':
52
45
  dependencies['next'] = '16.0.1';
53
46
  break;
@@ -58,6 +51,9 @@ export function generatePackageJson(config) {
58
51
  default:
59
52
  break;
60
53
  }
54
+ if (config.query) {
55
+ dependencies['@tanstack/react-query'] = '^5.90.7';
56
+ }
61
57
  if (config.libraries.includes('@undp/data-viz')) {
62
58
  const dataVizVer = `^${getLatestVersion('@undp/data-viz')}`
63
59
  dependencies['@undp/data-viz'] = dataVizVer;
@@ -0,0 +1,102 @@
1
+ export function generatePageForNext(query) {
2
+ return `'use client';
3
+
4
+ import { P } from '@undp/design-system-react/Typography';
5
+ import { Button } from '@undp/design-system-react/Button';
6
+ import Image from 'next/image';${query ? `
7
+ import { useQuery } from '@tanstack/react-query';
8
+ import { Spinner } from '@undp/design-system-react/Spinner';` :''}
9
+
10
+ import { useCounterActions, useCounter } from '@/stores/counter';${query ? `
11
+
12
+ function useTodoData() {
13
+ return useQuery({
14
+ queryKey: ['todos'],
15
+ queryFn: () =>
16
+ Promise.resolve([
17
+ { id: 1, name: 'Alice' },
18
+ { id: 2, name: 'Bob' },
19
+ { id: 3, name: 'Charlie' },
20
+ ]),
21
+ });
22
+ }` :''}
23
+
24
+ export default function Home() {
25
+ const count = useCounter();
26
+ const { increment, decrement } = useCounterActions();${query ? `
27
+
28
+ const { data, isLoading, isError } = useTodoData();
29
+
30
+ if (isLoading) return <Spinner size='lg' className='my-20 m-auto' />;
31
+
32
+ if (isError) return <>Error</>;` : ''}
33
+ return (
34
+ <>
35
+ <div className='flex gap-4 items-center justify-center my-8 mx-auto'>
36
+ <Image
37
+ className='dark:invert'
38
+ src='/imgs/next.svg'
39
+ alt='Next.js logo'
40
+ width={100}
41
+ height={20}
42
+ priority
43
+ />
44
+ <P marginBottom='none'>&</P>
45
+ <Image
46
+ className='dark:invert'
47
+ src='/imgs/Tailwind_CSS_Logo.svg'
48
+ alt='Tailwind logo'
49
+ width={40}
50
+ height={20}
51
+ priority
52
+ />
53
+ <P marginBottom='none'>&</P>
54
+ <Image
55
+ className='dark:invert'
56
+ src='/imgs/Zustand-logo.svg'
57
+ alt='Zustand logo'
58
+ width={40}
59
+ height={20}
60
+ priority
61
+ />
62
+ <P marginBottom='none'>&</P>
63
+ <Image
64
+ className='dark:invert'
65
+ src='/imgs/undp-logo-blue.svg'
66
+ alt='UNDP logo'
67
+ width={40}
68
+ height={20}
69
+ priority
70
+ />
71
+ </div>
72
+ <P marginBottom='xl' className='text-center'>
73
+ To get started, edit the App.tsx file.{' '}
74
+ <span className='font-bold'>Count: {count}</span>
75
+ </P>
76
+ <div className='flex gap-4 justify-center mb-8'>
77
+ <Button
78
+ variant='tertiary'
79
+ onClick={() => {
80
+ increment();
81
+ }}
82
+ >
83
+ Increase counter
84
+ </Button>
85
+ <Button
86
+ variant='tertiary'
87
+ onClick={() => {
88
+ decrement();
89
+ }}
90
+ >
91
+ Decrease counter
92
+ </Button>
93
+ </div>${query ? `
94
+ <P marginBottom='xl' className='text-center'>
95
+ Data loaded successfully. {data?.length} elements in the query.
96
+ </P>` : ''}
97
+ </>
98
+ );
99
+ }
100
+ `
101
+ }
102
+
@@ -6,3 +6,4 @@ export { generateLayoutForNext } from './generateLayoutForNext.js';
6
6
  export { generateStylesCss } from './generateStylesCss.js';
7
7
  export { generateNextEnvTypes } from './generateNextEnvTypes.js';
8
8
  export { generateEnv } from './generateEnv.js';
9
+ export { generatePageForNext } from './generatePageForNext.js';
package/bin/index.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  generateLayoutForNext,
18
18
  generateNextEnvTypes,
19
19
  generateEnv,
20
+ generatePageForNext,
20
21
  } from './generateFiles/index.js';
21
22
 
22
23
  function copyFolder(source, destination) {
@@ -45,14 +46,17 @@ async function main() {
45
46
  let tertiaryFolder = 'basic';
46
47
 
47
48
  switch (config.framework) {
48
- case 'vite-query':
49
- tertiaryFolder = 'query';
49
+ case 'vite-basic':
50
+ if (config.query)
51
+ tertiaryFolder = 'query';
52
+ else
53
+ tertiaryFolder = 'basic';
50
54
  break;
51
55
  case 'vite-router':
52
- tertiaryFolder = 'router';
53
- break;
54
- case 'vite-full':
55
- tertiaryFolder = 'query+router';
56
+ if (config.query)
57
+ tertiaryFolder = 'query+router';
58
+ else
59
+ tertiaryFolder = 'router';
56
60
  break;
57
61
  case 'next-auth':
58
62
  tertiaryFolder = 'auth';
@@ -73,9 +77,11 @@ async function main() {
73
77
  }
74
78
  } else {
75
79
  copyFolder(path.join(__dirname, `./templates/${baseFolder}/${tertiaryFolder}`), projectPath);
76
- fs.writeFileSync('app/layout.tsx', generateLayoutForNext(config.libraries.includes('@undp/data-viz'), config.projectName));
80
+ fs.writeFileSync('app/layout.tsx', generateLayoutForNext(config.libraries.includes('@undp/data-viz'), config.projectName, config.query));
77
81
  fs.writeFileSync('next-env.d.ts', generateNextEnvTypes());
82
+ fs.writeFileSync('app/page.tsx', generatePageForNext(config.query));
78
83
  if (config.framework === 'next-auth') fs.writeFileSync('.env.local', generateEnv());
84
+ if (config.query) copyFolder(path.join(__dirname, './templates/queryIntegrationForNext'), projectPath);
79
85
  }
80
86
  fs.writeFileSync('package.json', JSON.stringify(generatePackageJson(config), null, 2));
81
87
 
package/bin/promptUser.js CHANGED
@@ -25,17 +25,29 @@ export async function promptUser(name) {
25
25
  loop: false,
26
26
  message: chalk.yellow('๐Ÿ“ฆ Choose your setup (all include Tailwind for styling & Zustand for state management):\n'),
27
27
  choices: [
28
- { name: 'Vite โ€” Basic React setup', value: 'vite-basic' },
29
- { name: 'Vite + Query โ€” With TanStack Query', value: 'vite-query' },
28
+ { name: 'Vite โ€” Basic React setup (ideal for embedding)', value: 'vite-basic' },
30
29
  { name: 'Vite + Router โ€” With routing support', value: 'vite-router' },
31
- { name: 'Vite + Router + Query โ€” Full Vite setup', value: 'vite-full' },
32
30
  { name: 'Next.js โ€” SSR and file routing', value: 'next-basic' },
33
31
  { name: 'Next.js + Auth โ€” With authentication', value: 'next-auth' },
34
32
  ],
35
33
  default: 'vite-basic',
36
34
  },
35
+ ]);
36
+
37
+ const { addQuery } = await inquirer.prompt([
38
+ {
39
+ type: 'list',
40
+ name: 'addQuery',
41
+ message: chalk.yellow(
42
+ 'โš™๏ธ Add @tanstack/query for data fetching?'
43
+ ),
44
+ choices: ['Yes', 'No'],
45
+ default: 'No',
46
+ },
37
47
  ]);
38
48
 
49
+ const query = addQuery === 'Yes';
50
+
39
51
  const libraryChoices = [
40
52
  {
41
53
  name: '@undp/data-viz โ€” UNDP data visualization components',
@@ -97,6 +109,7 @@ export async function promptUser(name) {
97
109
  libraries,
98
110
  addStaticWebAppConfig,
99
111
  addPostCSSScripts,
100
- framework,
112
+ framework,
113
+ query,
101
114
  };
102
115
  }
@@ -1,4 +1,5 @@
1
1
  import type { Metadata } from 'next';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  import HeaderEl from '@/components/Header';
4
5
  import FooterEl from '@/components/Footer';
@@ -14,7 +15,7 @@ export const metadata: Metadata = {
14
15
  export default function RootLayout({
15
16
  children,
16
17
  }: Readonly<{
17
- children: React.ReactNode;
18
+ children: ReactNode;
18
19
  }>) {
19
20
  return (
20
21
  <html lang='en'>
@@ -1,4 +1,5 @@
1
1
  import type { Metadata } from 'next';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  import HeaderEl from '@/components/Header';
4
5
  import FooterEl from '@/components/Footer';
@@ -15,7 +16,7 @@ export const metadata: Metadata = {
15
16
  export default function RootLayout({
16
17
  children,
17
18
  }: Readonly<{
18
- children: React.ReactNode;
19
+ children: ReactNode;
19
20
  }>) {
20
21
  return (
21
22
  <html lang='en'>
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { ReactNode, useState } from 'react';
5
+
6
+ export default function TanStackQueryProvider({
7
+ children,
8
+ }: {
9
+ children: ReactNode;
10
+ }) {
11
+ // Create client only once
12
+ const [queryClient] = useState(
13
+ () =>
14
+ new QueryClient({
15
+ defaultOptions: {
16
+ queries: {
17
+ staleTime: 1000 * 60 * 60 * 24, // how long fetched data is considered โ€œfreshโ€ before it becomes โ€œstaleโ€ ๐Ÿกข 24 hrs
18
+ gcTime: 1000 * 60 * 60 * 24, // how long inactive (unused) query data stays in memory before being deleted ๐Ÿกข 24 hrs
19
+ },
20
+ },
21
+ }),
22
+ );
23
+
24
+ return (
25
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
26
+ );
27
+ }
@@ -1,4 +1,5 @@
1
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  export function getContext() {
4
5
  const queryClient = new QueryClient({
@@ -18,7 +19,7 @@ export function Provider({
18
19
  children,
19
20
  queryClient,
20
21
  }: {
21
- children: React.ReactNode;
22
+ children: ReactNode;
22
23
  queryClient: QueryClient;
23
24
  }) {
24
25
  return (
@@ -1,4 +1,5 @@
1
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  export function getContext() {
4
5
  const queryClient = new QueryClient({
@@ -18,7 +19,7 @@ export function Provider({
18
19
  children,
19
20
  queryClient,
20
21
  }: {
21
- children: React.ReactNode;
22
+ children: ReactNode;
22
23
  queryClient: QueryClient;
23
24
  }) {
24
25
  return (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undp/create-app",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "UNDP's project scaffolding tool",
5
5
  "bin": {
6
6
  "create-undp-app": "./bin/index.js"