attio 0.0.1-experimental.20250408 → 0.0.1-experimental.20250409

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.
Files changed (56) hide show
  1. package/lib/api/api.js +97 -29
  2. package/lib/api/fetcher.js +31 -27
  3. package/lib/api/schemas.js +6 -0
  4. package/lib/auth/auth.js +127 -96
  5. package/lib/auth/keychain.js +82 -43
  6. package/lib/build/server/generate-server-entry.js +6 -6
  7. package/lib/build.js +2 -2
  8. package/lib/commands/build/build-javascript.js +1 -1
  9. package/lib/commands/build/validate-typescript.js +1 -1
  10. package/lib/commands/build.js +2 -2
  11. package/lib/commands/dev/boot.js +8 -8
  12. package/lib/commands/dev/bundle-javascript.js +1 -1
  13. package/lib/commands/dev/prepare-build-contexts.js +1 -1
  14. package/lib/commands/dev/upload.js +1 -18
  15. package/lib/commands/dev.js +5 -4
  16. package/lib/commands/init/create-project.js +7 -39
  17. package/lib/commands/init.js +6 -14
  18. package/lib/commands/login.js +8 -2
  19. package/lib/commands/logout.js +8 -2
  20. package/lib/commands/version/create/bundle-javascript.js +3 -3
  21. package/lib/commands/version/create.js +11 -11
  22. package/lib/commands/version/list.js +4 -4
  23. package/lib/commands/whoami.js +5 -6
  24. package/lib/errors.js +1 -0
  25. package/lib/print-errors.js +165 -0
  26. package/lib/spinners/determine-workspace.spinner.js +1 -24
  27. package/lib/spinners/get-app-slug-from-package-json.js +1 -20
  28. package/lib/util/copy-with-replace.js +2 -2
  29. package/lib/util/create-directory.js +1 -1
  30. package/lib/util/load-attio-cli-version.js +1 -26
  31. package/lib/util/upload-bundle.js +1 -1
  32. package/package.json +1 -1
  33. package/lib/auth/ensure-authed.js +0 -14
  34. package/lib/commands/dev/start-graphql-server.js +0 -32
  35. package/lib/commands/init/ask-language.js +0 -11
  36. package/lib/templates/javascript/.env +0 -12
  37. package/lib/templates/javascript/eslint.config.js +0 -48
  38. package/lib/templates/javascript/package.json +0 -26
  39. package/lib/templates/javascript/src/assets/icon.png +0 -0
  40. package/lib/templates/javascript/src/cat-fact.jsx +0 -16
  41. package/lib/templates/javascript/src/get-cat-fact.server.js +0 -6
  42. package/lib/templates/javascript/src/hello-world-action.jsx +0 -17
  43. package/lib/templates/javascript/src/hello-world-dialog.jsx +0 -26
  44. package/lib/templates/typescript/.eslintignore +0 -3
  45. package/lib/templates/typescript/src/assets/icon.png +0 -0
  46. /package/lib/{templates/typescript → template}/.env +0 -0
  47. /package/lib/{templates/javascript → template}/.eslintignore +0 -0
  48. /package/lib/{templates/common → template}/README.md +0 -0
  49. /package/lib/{templates/typescript → template}/eslint.config.js +0 -0
  50. /package/lib/{templates/typescript → template}/package.json +0 -0
  51. /package/lib/{templates/common → template}/src/assets/icon.png +0 -0
  52. /package/lib/{templates/typescript → template}/src/cat-fact.tsx +0 -0
  53. /package/lib/{templates/typescript → template}/src/get-cat-fact.server.ts +0 -0
  54. /package/lib/{templates/typescript → template}/src/hello-world-action.tsx +0 -0
  55. /package/lib/{templates/typescript → template}/src/hello-world-dialog.tsx +0 -0
  56. /package/lib/{templates/typescript → template}/tsconfig.json +0 -0
@@ -0,0 +1,165 @@
1
+ import chalk from "chalk";
2
+ import { APP } from "./env.js";
3
+ export function printFetcherError(message, { error }) {
4
+ process.stderr.write(`${chalk.red("✖ ")}${message}\n`);
5
+ switch (error.code) {
6
+ case "HTTP_ERROR":
7
+ process.stderr.write(`HTTP Error (${error.status}): ${error.message}\n`);
8
+ break;
9
+ case "INVALID_RESPONSE":
10
+ process.stderr.write(`Invalid response: ${error.message}\n`);
11
+ break;
12
+ case "NETWORK_ERROR":
13
+ process.stderr.write(`Network error: ${error.message}\n`);
14
+ break;
15
+ case "UNAUTHORIZED":
16
+ process.stderr.write(`Unauthorized. You must log in with "attio login"\n`);
17
+ break;
18
+ }
19
+ }
20
+ export function printUploadError(error) {
21
+ switch (error.code) {
22
+ case "BUNDLE_UPLOAD_ERROR":
23
+ process.stderr.write(chalk.red(`Error uploading bundle: ${error.upload_url}\n`));
24
+ break;
25
+ case "START_UPLOAD_ERROR":
26
+ printFetcherError("Error starting upload", error);
27
+ break;
28
+ case "COMPLETE_BUNDLE_UPLOAD_ERROR":
29
+ printFetcherError("Error completing bundle upload", error);
30
+ break;
31
+ default:
32
+ return error;
33
+ }
34
+ }
35
+ export function printCreateProjectError(error) {
36
+ switch (error.code) {
37
+ case "DIRECTORY_ALREADY_EXISTS":
38
+ process.stderr.write(chalk.red(`Directory ${error.path} already exists`));
39
+ break;
40
+ case "WRITE_ACCESS_DENIED":
41
+ process.stderr.write(chalk.red(`Write access denied to ${error.path}`));
42
+ break;
43
+ case "FAILED_TO_CREATE_DIRECTORY":
44
+ process.stderr.write(chalk.red(`Failed to create directory ${error.path}`));
45
+ break;
46
+ case "FAILED_TO_COPY_FILE":
47
+ process.stderr.write(chalk.red(`Failed to copy file ${error.src} to ${error.dest}`));
48
+ break;
49
+ case "FAILED_TO_LIST_FILES":
50
+ process.stderr.write(chalk.red(`Failed to list files in ${error.path}`));
51
+ break;
52
+ case "FAILED_TO_READ_FILE":
53
+ process.stderr.write(chalk.red(`Failed to read file ${error.path}`));
54
+ break;
55
+ case "FAILED_TO_WRITE_FILE":
56
+ process.stderr.write(chalk.red(`Failed to write file ${error.path}`));
57
+ break;
58
+ default:
59
+ return error;
60
+ }
61
+ }
62
+ export function printPackageJsonError({ code, error }) {
63
+ switch (code) {
64
+ case "MALFORMED_PACKAGE_JSON":
65
+ if (error.issues.length > 0) {
66
+ process.stderr.write(`${chalk.red("✖ ")}Malformed package.json: ${error.issues[0].message}\n`);
67
+ }
68
+ else {
69
+ process.stderr.write(`${chalk.red("✖ ")}Malformed package.json: ${error.message}\n`);
70
+ }
71
+ break;
72
+ case "FILE_SYSTEM_ERROR":
73
+ process.stderr.write(`${chalk.red("✖ ")}Failed to read package.json\n`);
74
+ break;
75
+ case "INVALID_JSON":
76
+ process.stderr.write(`${chalk.red("✖ ")}Invalid JSON in package.json: ${error}\n`);
77
+ break;
78
+ }
79
+ }
80
+ export function printCliVersionError({ error }) {
81
+ switch (error.code) {
82
+ case "UNABLE_TO_FIND_PACKAGE_JSON":
83
+ process.stderr.write(`${chalk.red("✖ ")}Failed to find package.json in ${error.path}\n`);
84
+ break;
85
+ case "UNABLE_TO_READ_PACKAGE_JSON":
86
+ process.stderr.write(`${chalk.red("✖ ")}Failed to read package.json: ${error.error}\n`);
87
+ break;
88
+ case "UNABLE_TO_PARSE_PACKAGE_JSON":
89
+ process.stderr.write(`${chalk.red("✖ ")}Failed to parse package.json: ${error.error}\n`);
90
+ break;
91
+ case "INVALID_PACKAGE_JSON":
92
+ process.stderr.write(`${chalk.red("✖ ")}Invalid package.json: ${error.error}\n`);
93
+ break;
94
+ case "ERROR_LOADING_PACKAGE_JSON":
95
+ process.stderr.write(`${chalk.red("✖ ")}Error loading package.json: ${error.error}\n`);
96
+ break;
97
+ case "NO_CLI_VERSION_FOUND":
98
+ process.stderr.write(`${chalk.red("✖ ")}No CLI version found in attio's package.json\n`);
99
+ break;
100
+ default:
101
+ return error;
102
+ }
103
+ }
104
+ export function printDetermineWorkspaceError(error) {
105
+ switch (error.code) {
106
+ case "FETCH_WORKSPACES_ERROR":
107
+ process.stderr.write(`Error fetching workspaces: ${error.error}
108
+
109
+ ${APP}/welcome/workspace-details
110
+ `);
111
+ break;
112
+ case "NO_WORKSPACE_FOUND":
113
+ process.stderr.write(`You are not the admin any workspace with the slug "${error.workspace_slug}". Either request permission from "${error.workspace_slug}" or create your own.
114
+
115
+ ${APP}/welcome/workspace-details
116
+ `);
117
+ break;
118
+ case "NO_WORKSPACES_FOUND":
119
+ process.stderr.write(`You are not the admin of any workspaces. Either request permission from an existing workspace or create your own.
120
+
121
+ ${APP}/welcome/workspace-details
122
+ `);
123
+ break;
124
+ default:
125
+ return error;
126
+ }
127
+ }
128
+ export function printKeychainError(error) {
129
+ switch (error.code) {
130
+ case "SAVE_KEYCHAIN_ERROR":
131
+ process.stderr.write(chalk.red(`Error saving token to keychain: ${error.error}`));
132
+ break;
133
+ case "LOAD_KEYCHAIN_ERROR":
134
+ process.stderr.write(chalk.red(`Error loading token from keychain: ${error.error}`));
135
+ break;
136
+ case "DELETE_KEYCHAIN_ERROR":
137
+ process.stderr.write(chalk.red(`Error deleting token from keychain: ${error.error}`));
138
+ break;
139
+ default:
140
+ return error;
141
+ }
142
+ }
143
+ export function printAuthenticationError(error) {
144
+ switch (error.code) {
145
+ case "GET_TOKEN_ERROR":
146
+ printFetcherError("Error getting token", error);
147
+ break;
148
+ case "REFRESH_TOKEN_ERROR":
149
+ printFetcherError("Error refreshing token", error);
150
+ break;
151
+ case "NO_AUTHORIZATION_CODE":
152
+ process.stderr.write(chalk.red(`No authorization code received`));
153
+ break;
154
+ case "OAUTH_STATE_MISMATCH":
155
+ process.stderr.write(chalk.red(`OAuth state mismatch, possible CSRF attack`));
156
+ break;
157
+ case "SAVE_KEYCHAIN_ERROR":
158
+ case "LOAD_KEYCHAIN_ERROR":
159
+ case "DELETE_KEYCHAIN_ERROR":
160
+ printKeychainError(error);
161
+ break;
162
+ default:
163
+ return error;
164
+ }
165
+ }
@@ -1,9 +1,7 @@
1
1
  import { select } from "@inquirer/prompts";
2
- import { APP } from "../env.js";
3
2
  import { spinnerify } from "../util/spinner.js";
4
3
  import { API } from "../api/api.js";
5
4
  import { isErrored, complete, errored } from "@attio/fetchable";
6
- import { printFetcherError } from "../api/fetcher.js";
7
5
  export async function determineWorkspace(workspaceSlug) {
8
6
  const workspacesResult = await spinnerify("Loading workspaces...", "Workspaces loaded", async () => await API.fetchWorkspaces());
9
7
  if (isErrored(workspacesResult)) {
@@ -16,7 +14,7 @@ export async function determineWorkspace(workspaceSlug) {
16
14
  return complete(workspace);
17
15
  }
18
16
  if (workspaceSlug) {
19
- return errored({ code: "NO_WORKSPACE_FOUND", workspaceSlug });
17
+ return errored({ code: "NO_WORKSPACE_FOUND", workspace_slug: workspaceSlug });
20
18
  }
21
19
  if (workspaces.length === 0) {
22
20
  return errored({ code: "NO_WORKSPACES_FOUND" });
@@ -35,24 +33,3 @@ export async function determineWorkspace(workspaceSlug) {
35
33
  process.stdout.write(`Using workspace: ${choice.name}`);
36
34
  return complete(choice);
37
35
  }
38
- export function printDetermineWorkspaceError(error) {
39
- switch (error.code) {
40
- case "FETCH_WORKSPACES_ERROR":
41
- printFetcherError("Error fetching workspaces", error.fetcherError);
42
- break;
43
- case "NO_WORKSPACE_FOUND":
44
- process.stderr.write(`You are not the admin any workspace with the slug "${error.workspaceSlug}". Either request permission from "${error.workspaceSlug}" or create your own.
45
-
46
- ${APP}/welcome/workspace-details
47
- `);
48
- break;
49
- case "NO_WORKSPACES_FOUND":
50
- process.stderr.write(`You are not the admin of any workspaces. Either request permission from an existing workspace or create your own.
51
-
52
- ${APP}/welcome/workspace-details
53
- `);
54
- break;
55
- default:
56
- return error;
57
- }
58
- }
@@ -2,7 +2,6 @@ import { readFileSync } from "fs";
2
2
  import { join } from "path";
3
3
  import { z } from "zod";
4
4
  import { complete, errored } from "@attio/fetchable";
5
- import chalk from "chalk";
6
5
  const packageJsonSchema = z.object({
7
6
  name: z.string({
8
7
  required_error: "No name field found in package.json",
@@ -15,7 +14,7 @@ export async function getAppSlugFromPackageJson() {
15
14
  const packageJsonRaw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
16
15
  const result = packageJsonSchema.safeParse(packageJsonRaw);
17
16
  if (!result.success) {
18
- return errored({ code: "MALFORMED_PACKAGE_JSON", error: result.error.issues[0]?.message });
17
+ return errored({ code: "MALFORMED_PACKAGE_JSON", error: result.error });
19
18
  }
20
19
  return complete(result.data.name);
21
20
  }
@@ -26,21 +25,3 @@ export async function getAppSlugFromPackageJson() {
26
25
  return errored({ code: "FILE_SYSTEM_ERROR", error });
27
26
  }
28
27
  }
29
- export function printPackageJsonError({ code, error }) {
30
- switch (code) {
31
- case "MALFORMED_PACKAGE_JSON":
32
- if (error) {
33
- process.stderr.write(`${chalk.red("✖ ")}Malformed package.json: ${error}\n`);
34
- }
35
- else {
36
- process.stderr.write(`${chalk.red("✖ ")}Malformed package.json\n`);
37
- }
38
- break;
39
- case "FILE_SYSTEM_ERROR":
40
- process.stderr.write(`${chalk.red("✖ ")}Failed to read package.json\n`);
41
- break;
42
- case "INVALID_JSON":
43
- process.stderr.write(`${chalk.red("✖ ")}Invalid JSON in package.json: ${error}\n`);
44
- break;
45
- }
46
- }
@@ -6,14 +6,14 @@ export async function copyWithTransform(srcDir, destDir, transform) {
6
6
  await fs.mkdir(destDir, { recursive: true });
7
7
  }
8
8
  catch {
9
- return errored({ code: "FAILED_TO_CREATE_DIRECTORY", directory: destDir });
9
+ return errored({ code: "FAILED_TO_CREATE_DIRECTORY", path: destDir });
10
10
  }
11
11
  let entries;
12
12
  try {
13
13
  entries = await fs.readdir(srcDir, { withFileTypes: true });
14
14
  }
15
15
  catch {
16
- return errored({ code: "FAILED_TO_LIST_FILES", directory: srcDir });
16
+ return errored({ code: "FAILED_TO_LIST_FILES", path: srcDir });
17
17
  }
18
18
  const results = await combineAsync(entries.map(async (entry) => {
19
19
  const srcPath = path.join(srcDir, entry.name);
@@ -21,7 +21,7 @@ export const createDirectory = async (name) => {
21
21
  catch {
22
22
  return errored({
23
23
  code: "WRITE_ACCESS_DENIED",
24
- directory: name,
24
+ path: name,
25
25
  });
26
26
  }
27
27
  };
@@ -3,7 +3,6 @@ import { findUpSync } from "find-up-simple";
3
3
  import { z } from "zod";
4
4
  import { fileURLToPath } from "url";
5
5
  import { errored, complete } from "@attio/fetchable";
6
- import chalk from "chalk";
7
6
  const FILE_NAME = "package.json";
8
7
  const packageJsonSchema = z.object({
9
8
  name: z.literal("attio"),
@@ -20,7 +19,7 @@ export function loadAttioCliVersion() {
20
19
  if (packageJsonPath === undefined) {
21
20
  return errored({
22
21
  code: "UNABLE_TO_FIND_PACKAGE_JSON",
23
- directory: cwd,
22
+ path: cwd,
24
23
  });
25
24
  }
26
25
  let contents;
@@ -60,27 +59,3 @@ export function loadAttioCliVersion() {
60
59
  }
61
60
  return complete(version);
62
61
  }
63
- export function printCliVersionError({ error }) {
64
- switch (error.code) {
65
- case "UNABLE_TO_FIND_PACKAGE_JSON":
66
- process.stderr.write(`${chalk.red("✖ ")}Failed to find package.json in ${error.directory}\n`);
67
- break;
68
- case "UNABLE_TO_READ_PACKAGE_JSON":
69
- process.stderr.write(`${chalk.red("✖ ")}Failed to read package.json: ${error.error}\n`);
70
- break;
71
- case "UNABLE_TO_PARSE_PACKAGE_JSON":
72
- process.stderr.write(`${chalk.red("✖ ")}Failed to parse package.json: ${error.error}\n`);
73
- break;
74
- case "INVALID_PACKAGE_JSON":
75
- process.stderr.write(`${chalk.red("✖ ")}Invalid package.json: ${error.error}\n`);
76
- break;
77
- case "ERROR_LOADING_PACKAGE_JSON":
78
- process.stderr.write(`${chalk.red("✖ ")}Error loading package.json: ${error.error}\n`);
79
- break;
80
- case "NO_CLI_VERSION_FOUND":
81
- process.stderr.write(`${chalk.red("✖ ")}No CLI version found in attio's package.json\n`);
82
- break;
83
- default:
84
- return error;
85
- }
86
- }
@@ -12,6 +12,6 @@ export async function uploadBundle(bundle, uploadUrl) {
12
12
  return complete(undefined);
13
13
  }
14
14
  catch (error) {
15
- return errored({ code: "BUNDLE_UPLOAD_ERROR", error, uploadUrl });
15
+ return errored({ code: "BUNDLE_UPLOAD_ERROR", upload_url: uploadUrl });
16
16
  }
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attio",
3
- "version": "0.0.1-experimental.20250408",
3
+ "version": "0.0.1-experimental.20250409",
4
4
  "bin": "lib/attio.js",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,14 +0,0 @@
1
- import { auth as authApi } from "./auth.js";
2
- import { deleteAuthToken, loadAuthToken } from "./keychain.js";
3
- async function auth() {
4
- await deleteAuthToken();
5
- if (process.env.NODE_ENV !== "test") {
6
- process.stdout.write("You need to log in with Attio. Press Enter to continue...\n\n");
7
- await new Promise((resolve) => process.stdin.once("data", resolve));
8
- }
9
- return await authApi();
10
- }
11
- export async function ensureAuthed() {
12
- const token = await loadAuthToken();
13
- return token ?? (await auth());
14
- }
@@ -1,32 +0,0 @@
1
- import { graphqlServer } from "@hono/graphql-server";
2
- import { serve } from "@hono/node-server";
3
- import fs from "fs";
4
- import { buildSchema } from "graphql";
5
- import { Hono } from "hono";
6
- import path, { dirname } from "path";
7
- import { fileURLToPath } from "url";
8
- import { findAvailablePort } from "../../util/find-available-port.js";
9
- export function startGraphqlServer(sendBack) {
10
- let server = null;
11
- const startServer = async () => {
12
- const currentFilePath = fileURLToPath(import.meta.url);
13
- const currentDirPath = dirname(currentFilePath);
14
- const schemaPath = path.resolve(currentDirPath, "..", "..", "schema.graphql");
15
- const schemaString = fs.readFileSync(schemaPath, "utf8");
16
- const port = await findAvailablePort(8700);
17
- const schema = buildSchema(schemaString);
18
- const app = new Hono();
19
- const rootResolver = () => {
20
- return {};
21
- };
22
- app.use("/graphql", graphqlServer({ schema, rootResolver, graphiql: true }));
23
- server = serve({ fetch: app.fetch, port });
24
- sendBack({ code: "GraphQL Server Started", port });
25
- };
26
- startServer();
27
- return () => {
28
- if (!server)
29
- return;
30
- server.close();
31
- };
32
- }
@@ -1,11 +0,0 @@
1
- import { select } from "@inquirer/prompts";
2
- export const LANGUAGES = [
3
- { name: "TypeScript (recommended)", value: "typescript" },
4
- { name: "JavaScript", value: "javascript" },
5
- ];
6
- export async function askLanguage() {
7
- return select({
8
- message: "What language would you like to use?",
9
- choices: LANGUAGES,
10
- });
11
- }
@@ -1,12 +0,0 @@
1
- # Add secrets that you want available in your .server.js files
2
- # IN DEV MODE ONLY here. In production, secrets should be managed
3
- # using the `attio secret` command.
4
- #
5
- # Example: SECRET_KEY=super-secret-value
6
- #
7
- # You will then be able to reference them in your code like so:
8
- # ```
9
- # import { env } from '@attio/extension-sdk';
10
- #
11
- # const secretValue = env('SECRET_KEY');
12
- # ```
@@ -1,48 +0,0 @@
1
- const {FlatCompat} = require("@eslint/eslintrc")
2
- const js = require("@eslint/js")
3
- const reactPlugin = require("eslint-plugin-react")
4
- const reactHooksPlugin = require("eslint-plugin-react-hooks")
5
- const attio = require("attio/lint")
6
-
7
- const compat = new FlatCompat({
8
- baseDirectory: __dirname,
9
- recommendedConfig: js.configs.recommended,
10
- })
11
-
12
- module.exports = [
13
- ...compat.extends("eslint:recommended"),
14
- ...compat.extends("plugin:react/recommended"),
15
- {
16
- files: ["src/**/*.{js,jsx}"],
17
- languageOptions: {
18
- parserOptions: {
19
- ecmaFeatures: {
20
- jsx: true,
21
- },
22
- ecmaVersion: 12,
23
- sourceType: "module",
24
- },
25
- },
26
- plugins: {
27
- "react": reactPlugin,
28
- "react-hooks": reactHooksPlugin,
29
- attio,
30
- },
31
- rules: {
32
- "react/jsx-key": "error",
33
- "react-hooks/rules-of-hooks": "error",
34
- "react-hooks/exhaustive-deps": "error",
35
- "attio/attio-client-import": "error",
36
- "attio/server-default-export": "error",
37
- "attio/form-submit-button": "error",
38
- },
39
- settings: {
40
- react: {
41
- version: "detect",
42
- },
43
- },
44
- },
45
- {
46
- ignores: ["node_modules/", "dist/"],
47
- },
48
- ]
@@ -1,26 +0,0 @@
1
- {
2
- "name": "slug-to-be-replaced",
3
- "description": "title-to-be-replaced",
4
- "private": true,
5
- "version": "0.0.1",
6
- "scripts": {
7
- "attio": "attio",
8
- "dev": "attio dev",
9
- "build": "attio build",
10
- "format": "yarn run -T prettier --ignore-path $(yarn run -T find-up .prettierignore) --check ./",
11
- "format-fix": "yarn run format --write ./",
12
- "clean": "rm -rf dist",
13
- "lint": "ATTIO_LINT_MODE='exhaustive' eslint 'src/**/*.{js,jsx}'"
14
- },
15
- "devDependencies": {
16
- "eslint": "^8.57.1",
17
- "eslint-plugin-react": "^7.37.4",
18
- "eslint-plugin-react-hooks": "^5.1.0"
19
- },
20
- "dependencies": {
21
- "@eslint/eslintrc": "^3.2.0",
22
- "@eslint/js": "^9.19.0",
23
- "attio": "latest",
24
- "react": "19.0.0"
25
- }
26
- }
@@ -1,16 +0,0 @@
1
- import React from "react"
2
- import {TextBlock, useAsyncCache} from "attio/client"
3
- import getCatFact from "./get-cat-fact.server"
4
-
5
- export function CatFact({recordId}) {
6
- // By passing in the recordId, the result will be cached for each recordId
7
- const {
8
- values: {catFact},
9
- // ^^^^^^^– this key matches
10
- // vvvvvvv– this key
11
- } = useAsyncCache({catFact: [getCatFact, recordId]})
12
- // ^^^^^^^^^^ ^^^^^^^^
13
- // async fn parameter(s)
14
-
15
- return <TextBlock align="center">{`"${catFact}"`}</TextBlock>
16
- }
@@ -1,6 +0,0 @@
1
- export default async function getCatFact(recordId) {
2
- // We don't really need the recordId for this API, but this is how we could use a parameter
3
- const response = await fetch(`https://catfact.ninja/fact?${recordId}`)
4
- const data = await response.json()
5
- return data.fact
6
- }
@@ -1,17 +0,0 @@
1
- import React from "react"
2
- import { showDialog } from "attio/client"
3
- import { HelloWorldDialog } from "./hello-world-dialog"
4
-
5
- export const recordAction = {
6
- id: "slug-to-be-replaced",
7
- label: "title-to-be-replaced",
8
- onTrigger: async ({recordId}) => {
9
- showDialog({
10
- title: "title-to-be-replaced",
11
- Dialog: () => {
12
- // This is a React component. It can use hooks and render other components.
13
- return <HelloWorldDialog recordId={recordId} />
14
- }
15
- })
16
- },
17
- }
@@ -1,26 +0,0 @@
1
- import React from "react"
2
- import {TextBlock} from "attio/client"
3
- import {CatFact} from "./cat-fact"
4
-
5
- const Loading = () => <TextBlock>Loading cat fact...</TextBlock>
6
-
7
- export function HelloWorldDialog({recordId}) {
8
- // A simple counter to demonstrate that this is just regular React code.
9
- const [seconds, setSeconds] = React.useState(0)
10
- React.useEffect(() => {
11
- const timeout = setTimeout(() => setSeconds(seconds + 1), 1000)
12
- return () => clearTimeout(timeout)
13
- }, [seconds])
14
-
15
- return (
16
- <>
17
- <TextBlock align="left">
18
- I am a dialog. I have been open for: {seconds} second{seconds === 1 ? "" : "s"}
19
- </TextBlock>
20
- {/* The hook in CatFact will suspend until the cat fact is loaded. */}
21
- <React.Suspense fallback={<Loading />}>
22
- <CatFact recordId={recordId} />
23
- </React.Suspense>
24
- </>
25
- )
26
- }
@@ -1,3 +0,0 @@
1
- package.json
2
- dist/**
3
- node_modules/**
File without changes
File without changes