]> gerrit.midnightthoughts Code Review - neoboard-miro-converter.git/commitdiff
Initial commit from Create Miro App
authorMTRNord <mtrnord1@gmail.com>
Sat, 11 Jan 2025 13:35:47 +0000 (14:35 +0100)
committerMTRNord <mtrnord1@gmail.com>
Sat, 11 Jan 2025 13:35:47 +0000 (14:35 +0100)
18 files changed:
.gitignore [new file with mode: 0644]
APP_SUBMISSION.md [new file with mode: 0644]
README-template.md [new file with mode: 0644]
global.d.ts [new file with mode: 0644]
next-env.d.ts [new file with mode: 0644]
package-lock.json [new file with mode: 0644]
package.json [new file with mode: 0644]
src/app/api/redirect/route.ts [new file with mode: 0644]
src/app/layout.tsx [new file with mode: 0644]
src/app/loading.tsx [new file with mode: 0644]
src/app/page.tsx [new file with mode: 0644]
src/assets/congratulations.png [new file with mode: 0644]
src/assets/style.css [new file with mode: 0644]
src/assets/welcome.png [new file with mode: 0644]
src/components/SDKInit.tsx [new file with mode: 0644]
src/components/SDKUsageDemo.tsx [new file with mode: 0644]
src/utils/initMiroAPI.ts [new file with mode: 0644]
tsconfig.json [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b05b37f
--- /dev/null
@@ -0,0 +1,24 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.next
+
+# testing
+/coverage
+
+# misc
+.DS_Store
+*.pem
+.idea
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env
+dist
diff --git a/APP_SUBMISSION.md b/APP_SUBMISSION.md
new file mode 100644 (file)
index 0000000..0d3d2f0
--- /dev/null
@@ -0,0 +1,5 @@
+## Submission to Miro Marketplace
+
+Congrats! You have finished building your app & you'd like to publish it for
+users. You can submit your app on the
+[Miro Marketplace](https://developers.miro.com/docs/submit-your-app) for review.
diff --git a/README-template.md b/README-template.md
new file mode 100644 (file)
index 0000000..9c58c60
--- /dev/null
@@ -0,0 +1,39 @@
+## Create a Miro app
+
+### How to start locally
+
+1. [Sign in](https://miro.com/login/) to Miro, and then create a
+   [Developer team](https://developers.miro.com/docs/create-a-developer-team)
+   under your user account.
+
+2. [Create an app in Miro](https://developers.miro.com/docs/build-your-first-hello-world-app#step-2-create-your-app-in-miro).
+
+- Click the **Create new app** button.
+- On the **Create new app** modal, give your app a name, assign it to your
+  Developer team, and then click **Create**.
+
+3. Configure the app:
+
+- In your account profile, go to **Your apps**, and then select the app you just
+  created to access its configuration page.
+- On the app configuration page, go to **App Credentials**, and copy the app
+  **Client ID** and **Client secret** values: you'll need to enter these values
+  in step 4 below.
+- Go to **App URL** and enter the following URL: `http://localhost:3000`
+- Go to **Redirect URI for OAuth2.0**, and enter the following redirect URL:
+  `http://localhost:3000/api/redirect`
+- Click **Options**. \
+  From the drop-down menu select **Use this URI for SDK authorization**.
+- Lastly, go to **Permissions**, and select the following permissions:
+  - `board:read`
+  - `board:write`
+
+4. Open the [`.env`](.env) file, and enter the app client ID and client secret
+   values that you saved at the beginning of step 3 above.
+5. Run `npm start` to start developing.
+
+When your server is up and running:
+
+- Go to [Miro.com](https://miro.com).
+- In your developer team, open a board.
+- To start your app, click the app icon in the app toolbar on the left.
diff --git a/global.d.ts b/global.d.ts
new file mode 100644 (file)
index 0000000..18b40cc
--- /dev/null
@@ -0,0 +1,2 @@
+// https://vitejs.dev/guide/features.html#typescript-compiler-options
+/// <reference types="vite/client" />
diff --git a/next-env.d.ts b/next-env.d.ts
new file mode 100644 (file)
index 0000000..4f11a03
--- /dev/null
@@ -0,0 +1,5 @@
+/// <reference types="next" />
+/// <reference types="next/image-types/global" />
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/package-lock.json b/package-lock.json
new file mode 100644 (file)
index 0000000..63ed533
--- /dev/null
@@ -0,0 +1,897 @@
+{
+  "name": "neoboard-exporter",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "neoboard-exporter",
+      "version": "0.1.0",
+      "license": "MIT",
+      "dependencies": {
+        "@mirohq/miro-api": "^2.0.0",
+        "cookie": "^0.5.0",
+        "dotenv": "^16.0.3",
+        "mirotone": "5",
+        "next": "14.0.1",
+        "react": "^18.2.0",
+        "react-dom": "^18.2.0"
+      },
+      "devDependencies": {
+        "@mirohq/websdk-types": "latest",
+        "@types/cookie": "^0.5.1",
+        "@types/node": "^18.8.2",
+        "@types/react": "^18.0.24",
+        "typescript": "4.9.5"
+      }
+    },
+    "node_modules/@mirohq/design-tokens": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@mirohq/design-tokens/-/design-tokens-5.1.1.tgz",
+      "integrity": "sha512-PcCA7HlumSSaRkAIlrB4+CKhFyF+w93q1SnbIL6zPM69hzN9kJD7zNUk2ngGJpDcQseRyoE+uJkN8f+WRvp0Lg=="
+    },
+    "node_modules/@mirohq/miro-api": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/@mirohq/miro-api/-/miro-api-2.2.2.tgz",
+      "integrity": "sha512-KoBjjMv8rXXSQtvAamT1YQ94vZ+Lofw/Gp4hvHYHzlHtc4A3UHkUl6vgBh2O+eHf5j/sUCJ+xe7JbQsyHoZVng==",
+      "license": "MIT",
+      "dependencies": {
+        "form-data": "^4.0.0",
+        "node-fetch": "^2.7.0"
+      }
+    },
+    "node_modules/@mirohq/websdk-types": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/@mirohq/websdk-types/-/websdk-types-2.16.1.tgz",
+      "integrity": "sha512-dv3RX+aP7QvmXXpishm72BeWR/iDxwXdnLufLO5glgOTKIBGiRHuju2WL7tSOOmuttw2LjVc03zSBDYK692o0Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "typescript": ">=4.6.3 || ~5"
+      },
+      "peerDependencies": {
+        "@mirohq/prettier-config": "*",
+        "@typescript-eslint/eslint-plugin": "*",
+        "@typescript-eslint/parser": "*",
+        "eslint": "*",
+        "eslint-config-airbnb-typescript": "*",
+        "eslint-plugin-import": "*",
+        "husky": "*",
+        "lint-staged": "*",
+        "prettier": "*"
+      },
+      "peerDependenciesMeta": {
+        "@mirohq/prettier-config": {
+          "optional": true
+        },
+        "@typescript-eslint/eslint-plugin": {
+          "optional": true
+        },
+        "@typescript-eslint/parser": {
+          "optional": true
+        },
+        "eslint": {
+          "optional": true
+        },
+        "eslint-config-airbnb-typescript": {
+          "optional": true
+        },
+        "eslint-plugin-import": {
+          "optional": true
+        },
+        "husky": {
+          "optional": true
+        },
+        "lint-staged": {
+          "optional": true
+        },
+        "prettier": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@next/env": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.1.tgz",
+      "integrity": "sha512-Ms8ZswqY65/YfcjrlcIwMPD7Rg/dVjdLapMcSHG26W6O67EJDF435ShW4H4LXi1xKO1oRc97tLXUpx8jpLe86A==",
+      "license": "MIT"
+    },
+    "node_modules/@next/swc-darwin-arm64": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.1.tgz",
+      "integrity": "sha512-JyxnGCS4qT67hdOKQ0CkgFTp+PXub5W1wsGvIq98TNbF3YEIN7iDekYhYsZzc8Ov0pWEsghQt+tANdidITCLaw==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-darwin-x64": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.1.tgz",
+      "integrity": "sha512-625Z7bb5AyIzswF9hvfZWa+HTwFZw+Jn3lOBNZB87lUS0iuCYDHqk3ujuHCkiyPtSC0xFBtYDLcrZ11mF/ap3w==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-linux-arm64-gnu": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.1.tgz",
+      "integrity": "sha512-iVpn3KG3DprFXzVHM09kvb//4CNNXBQ9NB/pTm8LO+vnnnaObnzFdS5KM+w1okwa32xH0g8EvZIhoB3fI3mS1g==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-linux-arm64-musl": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.1.tgz",
+      "integrity": "sha512-mVsGyMxTLWZXyD5sen6kGOTYVOO67lZjLApIj/JsTEEohDDt1im2nkspzfV5MvhfS7diDw6Rp/xvAQaWZTv1Ww==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-linux-x64-gnu": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.1.tgz",
+      "integrity": "sha512-wMqf90uDWN001NqCM/auRl3+qVVeKfjJdT9XW+RMIOf+rhUzadmYJu++tp2y+hUbb6GTRhT+VjQzcgg/QTD9NQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-linux-x64-musl": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.1.tgz",
+      "integrity": "sha512-ol1X1e24w4j4QwdeNjfX0f+Nza25n+ymY0T2frTyalVczUmzkVD7QGgPTZMHfR1aLrO69hBs0G3QBYaj22J5GQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-win32-arm64-msvc": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.1.tgz",
+      "integrity": "sha512-WEmTEeWs6yRUEnUlahTgvZteh5RJc4sEjCQIodJlZZ5/VJwVP8p2L7l6VhzQhT4h7KvLx/Ed4UViBdne6zpIsw==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-win32-ia32-msvc": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.1.tgz",
+      "integrity": "sha512-oFpHphN4ygAgZUKjzga7SoH2VGbEJXZa/KL8bHCAwCjDWle6R1SpiGOdUdA8EJ9YsG1TYWpzY6FTbUA+iAJeww==",
+      "cpu": [
+        "ia32"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@next/swc-win32-x64-msvc": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.1.tgz",
+      "integrity": "sha512-FFp3nOJ/5qSpeWT0BZQ+YE1pSMk4IMpkME/1DwKBwhg4mJLB9L+6EXuJi4JEwaJdl5iN+UUlmUD3IsR1kx5fAg==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@swc/helpers": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
+      "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@types/cookie": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.4.tgz",
+      "integrity": "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "18.19.70",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.70.tgz",
+      "integrity": "sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/@types/prop-types": {
+      "version": "15.7.14",
+      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+      "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/react": {
+      "version": "18.3.18",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
+      "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/prop-types": "*",
+        "csstype": "^3.0.2"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/busboy": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+      "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+      "dependencies": {
+        "streamsearch": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=10.16.0"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001692",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
+      "integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0"
+    },
+    "node_modules/client-only": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+      "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+      "license": "MIT"
+    },
+    "node_modules/clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/clone-buffer": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz",
+      "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/clone-stats": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz",
+      "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==",
+      "license": "MIT"
+    },
+    "node_modules/cloneable-readable": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz",
+      "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.1",
+        "process-nextick-args": "^2.0.0",
+        "readable-stream": "^2.3.5"
+      }
+    },
+    "node_modules/cloneable-readable/node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+      "license": "MIT"
+    },
+    "node_modules/cloneable-readable/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/cloneable-readable/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "16.4.7",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+      "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+      "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/glob-to-regexp": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+      "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "license": "ISC"
+    },
+    "node_modules/gulp-file": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/gulp-file/-/gulp-file-0.4.0.tgz",
+      "integrity": "sha512-3NPCJpAPpbNoV2aml8T96OK3Aof4pm4PMOIa1jSQbMNSNUUXdZ5QjVgLXLStjv0gg9URcETc7kvYnzXdYXUWug==",
+      "license": "BSD",
+      "dependencies": {
+        "through2": "^0.4.1",
+        "vinyl": "^2.1.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/isarray": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+      "license": "MIT"
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "license": "MIT"
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mirotone": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/mirotone/-/mirotone-5.3.0.tgz",
+      "integrity": "sha512-PsAMyy48OwGleHWtOCGscjcqt2U5tFbFvTKdUeamv9+bGvt7lQHHkpccQLCqdKfMJeAIySRAJggMDmm7fls+kQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@mirohq/design-tokens": "5.1.1",
+        "gulp-file": "^0.4.0"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.8",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/next": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/next/-/next-14.0.1.tgz",
+      "integrity": "sha512-s4YaLpE4b0gmb3ggtmpmV+wt+lPRuGtANzojMQ2+gmBpgX9w5fTbjsy6dXByBuENsdCX5pukZH/GxdFgO62+pA==",
+      "license": "MIT",
+      "dependencies": {
+        "@next/env": "14.0.1",
+        "@swc/helpers": "0.5.2",
+        "busboy": "1.6.0",
+        "caniuse-lite": "^1.0.30001406",
+        "postcss": "8.4.31",
+        "styled-jsx": "5.1.1",
+        "watchpack": "2.4.0"
+      },
+      "bin": {
+        "next": "dist/bin/next"
+      },
+      "engines": {
+        "node": ">=18.17.0"
+      },
+      "optionalDependencies": {
+        "@next/swc-darwin-arm64": "14.0.1",
+        "@next/swc-darwin-x64": "14.0.1",
+        "@next/swc-linux-arm64-gnu": "14.0.1",
+        "@next/swc-linux-arm64-musl": "14.0.1",
+        "@next/swc-linux-x64-gnu": "14.0.1",
+        "@next/swc-linux-x64-musl": "14.0.1",
+        "@next/swc-win32-arm64-msvc": "14.0.1",
+        "@next/swc-win32-ia32-msvc": "14.0.1",
+        "@next/swc-win32-x64-msvc": "14.0.1"
+      },
+      "peerDependencies": {
+        "@opentelemetry/api": "^1.1.0",
+        "react": "^18.2.0",
+        "react-dom": "^18.2.0",
+        "sass": "^1.3.0"
+      },
+      "peerDependenciesMeta": {
+        "@opentelemetry/api": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/object-keys": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz",
+      "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==",
+      "license": "MIT"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/postcss": {
+      "version": "8.4.31",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+      "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.6",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.0.2"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "license": "MIT"
+    },
+    "node_modules/react": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.2"
+      },
+      "peerDependencies": {
+        "react": "^18.3.1"
+      }
+    },
+    "node_modules/readable-stream": {
+      "version": "1.0.34",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+      "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.1",
+        "isarray": "0.0.1",
+        "string_decoder": "~0.10.x"
+      }
+    },
+    "node_modules/remove-trailing-separator": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+      "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
+      "license": "ISC"
+    },
+    "node_modules/replace-ext": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz",
+      "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.2",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/streamsearch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+      "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/string_decoder": {
+      "version": "0.10.31",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
+      "license": "MIT"
+    },
+    "node_modules/styled-jsx": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
+      "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
+      "license": "MIT",
+      "dependencies": {
+        "client-only": "0.0.1"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+      },
+      "peerDependenciesMeta": {
+        "@babel/core": {
+          "optional": true
+        },
+        "babel-plugin-macros": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/through2": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz",
+      "integrity": "sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==",
+      "license": "MIT",
+      "dependencies": {
+        "readable-stream": "~1.0.17",
+        "xtend": "~2.1.1"
+      }
+    },
+    "node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "license": "MIT"
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "license": "0BSD"
+    },
+    "node_modules/typescript": {
+      "version": "4.9.5",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=4.2.0"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/vinyl": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz",
+      "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==",
+      "license": "MIT",
+      "dependencies": {
+        "clone": "^2.1.1",
+        "clone-buffer": "^1.0.0",
+        "clone-stats": "^1.0.0",
+        "cloneable-readable": "^1.0.0",
+        "remove-trailing-separator": "^1.0.1",
+        "replace-ext": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/watchpack": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
+      "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
+      "license": "MIT",
+      "dependencies": {
+        "glob-to-regexp": "^0.4.1",
+        "graceful-fs": "^4.1.2"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
+    "node_modules/xtend": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",
+      "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==",
+      "dependencies": {
+        "object-keys": "~0.4.0"
+      },
+      "engines": {
+        "node": ">=0.4"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..9dc1234
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "name": "neoboard-exporter",
+  "version": "0.1.0",
+  "license": "MIT",
+  "scripts": {
+    "build": "next build",
+    "start": "next dev",
+    "lint": "next lint"
+  },
+  "dependencies": {
+    "next": "14.0.1",
+    "@mirohq/miro-api": "^2.0.0",
+    "mirotone": "5",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "dotenv": "^16.0.3",
+    "cookie": "^0.5.0"
+  },
+  "devDependencies": {
+    "@mirohq/websdk-types": "latest",
+    "typescript": "4.9.5",
+    "@types/node": "^18.8.2",
+    "@types/react": "^18.0.24",
+    "@types/cookie": "^0.5.1"
+  }
+}
diff --git a/src/app/api/redirect/route.ts b/src/app/api/redirect/route.ts
new file mode 100644 (file)
index 0000000..3523d38
--- /dev/null
@@ -0,0 +1,22 @@
+import type {NextRequest} from 'next/server';
+import {redirect} from 'next/navigation';
+import initMiroAPI from '../../../utils/initMiroAPI';
+
+// handle redirect with code and exchange it for the access token
+export async function GET(request: NextRequest) {
+  const {miro, userId} = initMiroAPI();
+
+  // Make sure the code is in query parameters
+  const code = request.nextUrl.searchParams.get('code');
+  if (typeof code !== 'string') {
+    redirect('/?missing-code');
+    return;
+  }
+
+  try {
+    await miro.exchangeCodeForAccessToken(userId, code);
+  } catch (error) {
+    redirect('/?error');
+  }
+  redirect(`/`);
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644 (file)
index 0000000..23a2e4c
--- /dev/null
@@ -0,0 +1,49 @@
+import React, {PropsWithChildren} from 'react';
+import Image from 'next/image';
+import Script from 'next/script';
+
+import congratulations from '../assets/congratulations.png';
+import {SDKUsageDemo} from '../components/SDKUsageDemo';
+import {MiroSDKInit} from '../components/SDKInit';
+
+export default function RootLayout({children}: PropsWithChildren) {
+  return (
+    <html>
+      <body>
+        <Script
+          src="https://miro.com/app/static/sdk/v2/miro.js"
+          strategy="beforeInteractive"
+        />
+        <MiroSDKInit />
+        <div id="root">
+          <div className="grid">
+            <div className="cs1 ce12">
+              <Image src={congratulations} alt="" />
+              <h1>Congratulations!</h1>
+              <p>You've just created your first Miro app!</p>
+            </div>
+            <div className="cs1 ce12">
+              <SDKUsageDemo />
+            </div>
+            <hr className="cs1 ce12" />
+            <div className="cs1 ce12">{children}</div>
+            <hr className="cs1 ce12" />
+            <div className="cs1 ce12">
+              <p>
+                To explore more and build your own app, see the Miro Developer
+                Platform documentation.
+              </p>
+              <a
+                className="button button-secondary"
+                target="_blank"
+                href="https://developers.miro.com"
+              >
+                Read the documentation
+              </a>
+            </div>
+          </div>
+        </div>
+      </body>
+    </html>
+  );
+}
diff --git a/src/app/loading.tsx b/src/app/loading.tsx
new file mode 100644 (file)
index 0000000..f9709a3
--- /dev/null
@@ -0,0 +1,5 @@
+const Loading = () => {
+  return <div>...Loading</div>;
+};
+
+export default Loading;
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644 (file)
index 0000000..4e0a56a
--- /dev/null
@@ -0,0 +1,57 @@
+import React from 'react';
+import {Board} from '@mirohq/miro-api';
+
+import initMiroAPI from '../utils/initMiroAPI';
+import '../assets/style.css';
+
+const getBoards = async () => {
+  const {miro, userId} = initMiroAPI();
+
+  // redirect to auth url if user has not authorized the app
+  if (!userId || !(await miro.isAuthorized(userId))) {
+    return {
+      authUrl: miro.getAuthUrl(),
+    };
+  }
+
+  const api = miro.as(userId);
+
+  const boards: Board[] = [];
+  for await (const board of api.getAllBoards()) {
+    boards.push(board);
+  }
+
+  return {
+    boards,
+  };
+};
+
+export default async function Page() {
+  const {boards, authUrl} = await getBoards();
+
+  return (
+    <div>
+      <h3>API usage demo</h3>
+      <p className="p-small">API Calls need to be authenticated</p>
+      <p>
+        Apps that use the API usually would run on your own domain. During
+        development, test on http://localhost:3000
+      </p>
+      {authUrl ? (
+        <a className="button button-primary" href={authUrl} target="_blank">
+          Login
+        </a>
+      ) : (
+        <>
+          <p>This is a list of all the boards that your user has access to:</p>
+
+          <ul>
+            {boards?.map((board) => (
+              <li key={board.name}>{board.name}</li>
+            ))}
+          </ul>
+        </>
+      )}
+    </div>
+  );
+}
diff --git a/src/assets/congratulations.png b/src/assets/congratulations.png
new file mode 100644 (file)
index 0000000..1c2dfd0
Binary files /dev/null and b/src/assets/congratulations.png differ
diff --git a/src/assets/style.css b/src/assets/style.css
new file mode 100644 (file)
index 0000000..960cfa6
--- /dev/null
@@ -0,0 +1,22 @@
+@import 'mirotone/dist/styles.css';
+
+*,
+*:before,
+*:after {
+  box-sizing: border-box;
+}
+
+body {
+  display: flex;
+}
+
+#root {
+  width: 100%;
+  overflow: auto;
+  padding: var(--space-medium);
+}
+
+img {
+  max-width: 100%;
+  height: auto;
+}
diff --git a/src/assets/welcome.png b/src/assets/welcome.png
new file mode 100644 (file)
index 0000000..e66f0e7
Binary files /dev/null and b/src/assets/welcome.png differ
diff --git a/src/components/SDKInit.tsx b/src/components/SDKInit.tsx
new file mode 100644 (file)
index 0000000..42fb208
--- /dev/null
@@ -0,0 +1,13 @@
+'use client';
+
+import {useEffect} from 'react';
+
+export const MiroSDKInit = () => {
+  useEffect(() => {
+    miro.board.ui.on('icon:click', async () => {
+      await miro.board.ui.openPanel({url: '/'});
+    });
+  });
+
+  return null;
+};
diff --git a/src/components/SDKUsageDemo.tsx b/src/components/SDKUsageDemo.tsx
new file mode 100644 (file)
index 0000000..9b324d8
--- /dev/null
@@ -0,0 +1,33 @@
+'use client';
+import {FC} from 'react';
+
+async function addSticky() {
+  const stickyNote = await miro.board.createStickyNote({
+    content: 'Hello, World!',
+  });
+  await miro.board.viewport.zoomTo(stickyNote);
+}
+
+export const SDKUsageDemo: FC = () => {
+  return (
+    <div>
+      <h3>SDK Usage Demo</h3>
+      <p className="p-small">SDK doesnt need to be authenticated.</p>
+      <p>
+        Apps that use the SDK should run inside a Miro board. During
+        development, you can open this app inside a{' '}
+        <a href="https://developers.miro.com/docs/build-your-first-hello-world-app#step-2-try-out-your-app-in-miro">
+          Miro board
+        </a>
+        .
+      </p>
+      <button
+        type="button"
+        onClick={addSticky}
+        className="button button-primary"
+      >
+        Add a sticky
+      </button>
+    </div>
+  );
+};
diff --git a/src/utils/initMiroAPI.ts b/src/utils/initMiroAPI.ts
new file mode 100644 (file)
index 0000000..85ccff5
--- /dev/null
@@ -0,0 +1,39 @@
+import {Miro} from '@mirohq/miro-api';
+import {cookies} from 'next/headers';
+import {State} from '@mirohq/miro-api/dist/storage';
+
+const tokensCookie = 'miro_tokens';
+
+export default function initMiroAPI() {
+  const cookieInstance = cookies();
+
+  const getCookieValue = (key: string = tokensCookie) => {
+    // Load state (tokens) from a cookie if it's set
+    try {
+      return JSON.parse(cookieInstance.get(key)?.value!) as State;
+    } catch (err) {
+      return null;
+    }
+  };
+
+  // setup a Miro instance that loads tokens from cookies
+  return {
+    miro: new Miro({
+      storage: {
+        get: () => {
+          return getCookieValue();
+        },
+        set: (_, state) => {
+          cookieInstance.set(tokensCookie, JSON.stringify(state), {
+            path: '/',
+            httpOnly: true,
+            sameSite: 'none',
+            secure: true,
+          });
+        },
+      },
+    }),
+    // User id might be undefined if the user is not logged in yet, we will know it after the redirect happened
+    userId: getCookieValue()?.userId || '',
+  };
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644 (file)
index 0000000..7379c0e
--- /dev/null
@@ -0,0 +1,43 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "es2020",
+    "lib": [
+      "esnext",
+      "dom"
+    ],
+    "jsx": "preserve",
+    "moduleResolution": "node",
+    "strict": true,
+    "sourceMap": true,
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "noEmit": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noImplicitReturns": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "typeRoots": [
+      "./node_modules/@types",
+      "./node_modules/@mirohq"
+    ],
+    "allowJs": true,
+    "incremental": true,
+    "isolatedModules": true,
+    "plugins": [
+      {
+        "name": "next"
+      }
+    ]
+  },
+  "include": [
+    "src",
+    "app",
+    "*.ts",
+    ".next/types/**/*.ts"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
+}