@salesforce/webapp-template-feature-react-file-upload-experimental 1.55.0
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/LICENSE.txt +82 -0
- package/README.md +102 -0
- package/dist/.a4drules/README.md +35 -0
- package/dist/.a4drules/a4d-webapp-generate.md +27 -0
- package/dist/.a4drules/build-validation.md +78 -0
- package/dist/.a4drules/code-quality.md +137 -0
- package/dist/.a4drules/graphql/tools/knowledge/lds-explore-graphql-schema.md +227 -0
- package/dist/.a4drules/graphql/tools/knowledge/lds-generate-graphql-mutationquery.md +212 -0
- package/dist/.a4drules/graphql/tools/knowledge/lds-generate-graphql-readquery.md +185 -0
- package/dist/.a4drules/graphql/tools/knowledge/lds-guide-graphql.md +205 -0
- package/dist/.a4drules/graphql/tools/schemas/shared.graphqls +1150 -0
- package/dist/.a4drules/graphql.md +409 -0
- package/dist/.a4drules/images.md +13 -0
- package/dist/.a4drules/react.md +387 -0
- package/dist/.a4drules/react_image_processing.md +45 -0
- package/dist/.a4drules/typescript.md +224 -0
- package/dist/.a4drules/ui-layout.md +23 -0
- package/dist/.a4drules/webapp-nav-and-placeholders.md +33 -0
- package/dist/.a4drules/webapp-no-node-e.md +25 -0
- package/dist/.a4drules/webapp-ui-first.md +32 -0
- package/dist/.a4drules/webapp.md +75 -0
- package/dist/.forceignore +15 -0
- package/dist/.husky/pre-commit +4 -0
- package/dist/.prettierignore +11 -0
- package/dist/.prettierrc +17 -0
- package/dist/AGENT.md +75 -0
- package/dist/CHANGELOG.md +803 -0
- package/dist/README.md +18 -0
- package/dist/config/project-scratch-def.json +13 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/.graphqlrc.yml +2 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/.prettierignore +9 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/.prettierrc +11 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/build/vite.config.d.ts +2 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/build/vite.config.js +93 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/codegen.yml +94 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/e2e/app.spec.ts +17 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/eslint.config.js +141 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/feature-react-file-upload.webapplication-meta.xml +7 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/index.html +13 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/package-lock.json +18396 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/package.json +66 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/playwright.config.ts +24 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/scripts/get-graphql-schema.mjs +68 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/scripts/rewrite-e2e-assets.mjs +23 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/api/fileUpload.ts +154 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/api/graphql-operations-types.ts +116 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/api/utils/accounts.ts +41 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/api/utils/query/highRevenueAccountsQuery.graphql +29 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/app.tsx +22 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/appLayout.tsx +9 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/icons/book.svg +3 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/icons/copy.svg +4 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/icons/rocket.svg +3 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/icons/star.svg +3 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/images/codey-1.png +0 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/images/codey-2.png +0 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/images/codey-3.png +0 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/images/vibe-codey.svg +194 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/symbols.svg +1 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/assets/utility.svg +1 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUpload.tsx +83 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDialog.tsx +79 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDropZone.tsx +82 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadFileItem.tsx +99 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadIcons.tsx +58 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/alert.tsx +69 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/button.tsx +67 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/card.tsx +92 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/dialog.tsx +143 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/field.tsx +222 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/index.ts +84 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/input.tsx +19 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/label.tsx +19 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/pagination.tsx +112 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/select.tsx +183 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/separator.tsx +26 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/skeleton.tsx +14 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/spinner.tsx +15 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/table.tsx +87 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/tabs.tsx +78 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components.json +18 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/hooks/useFileUpload.ts +299 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/hooks/useFileUploadDialog.ts +70 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +56 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/lib/utils.ts +6 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/navigationMenu.tsx +80 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/pages/Home.tsx +25 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/pages/NotFound.tsx +18 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/router-utils.tsx +35 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +22 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/styles/global.css +135 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/types/fileUpload.ts +26 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/utils/fileUploadUtils.ts +44 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/utils/labels.ts +21 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/tsconfig.json +36 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/tsconfig.node.json +13 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/vite-env.d.ts +1 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/vite.config.ts +43 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/vitest-env.d.ts +2 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/vitest.config.ts +11 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/vitest.setup.ts +1 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/webapplication.json +7 -0
- package/dist/jest.config.js +6 -0
- package/dist/package.json +38 -0
- package/dist/scripts/apex/hello.apex +10 -0
- package/dist/scripts/soql/account.soql +6 -0
- package/dist/sfdx-project.json +12 -0
- package/package.json +53 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/api/fileUpload.ts +154 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/appLayout.tsx +9 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/assets/symbols.svg +1 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/assets/utility.svg +1 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUpload.tsx +83 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDialog.tsx +79 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDropZone.tsx +82 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadFileItem.tsx +99 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadIcons.tsx +58 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/hooks/useFileUpload.ts +299 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/hooks/useFileUploadDialog.ts +70 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +56 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/pages/Home.tsx +25 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +17 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/types/fileUpload.ts +26 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/utils/fileUploadUtils.ts +44 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/utils/labels.ts +21 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/vite.config.ts +43 -0
package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/styles/global.css
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
@layer base {
|
|
4
|
+
html,
|
|
5
|
+
body,
|
|
6
|
+
#root {
|
|
7
|
+
@apply min-h-screen;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
@apply antialiased bg-white;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@import "tw-animate-css";
|
|
16
|
+
@import "shadcn/tailwind.css";
|
|
17
|
+
|
|
18
|
+
@custom-variant dark (&:is(.dark *));
|
|
19
|
+
|
|
20
|
+
@theme inline {
|
|
21
|
+
--color-background: var(--background);
|
|
22
|
+
--color-foreground: var(--foreground);
|
|
23
|
+
--color-card: var(--card);
|
|
24
|
+
--color-card-foreground: var(--card-foreground);
|
|
25
|
+
--color-popover: var(--popover);
|
|
26
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
27
|
+
--color-primary: var(--primary);
|
|
28
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
29
|
+
--color-secondary: var(--secondary);
|
|
30
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
31
|
+
--color-muted: var(--muted);
|
|
32
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
33
|
+
--color-accent: var(--accent);
|
|
34
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
35
|
+
--color-destructive: var(--destructive);
|
|
36
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
37
|
+
--color-border: var(--border);
|
|
38
|
+
--color-input: var(--input);
|
|
39
|
+
--color-ring: var(--ring);
|
|
40
|
+
--color-chart-1: var(--chart-1);
|
|
41
|
+
--color-chart-2: var(--chart-2);
|
|
42
|
+
--color-chart-3: var(--chart-3);
|
|
43
|
+
--color-chart-4: var(--chart-4);
|
|
44
|
+
--color-chart-5: var(--chart-5);
|
|
45
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
46
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
47
|
+
--radius-lg: var(--radius);
|
|
48
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
49
|
+
--color-sidebar: var(--sidebar);
|
|
50
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
51
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
52
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
53
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
54
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
55
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
56
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:root {
|
|
60
|
+
--radius: 0.625rem;
|
|
61
|
+
--background: oklch(1 0 0);
|
|
62
|
+
--foreground: oklch(0.145 0 0);
|
|
63
|
+
--card: oklch(1 0 0);
|
|
64
|
+
--card-foreground: oklch(0.145 0 0);
|
|
65
|
+
--popover: oklch(1 0 0);
|
|
66
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
67
|
+
--primary: oklch(0.205 0 0);
|
|
68
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
69
|
+
--secondary: oklch(0.97 0 0);
|
|
70
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
71
|
+
--muted: oklch(0.97 0 0);
|
|
72
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
73
|
+
--accent: oklch(0.97 0 0);
|
|
74
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
75
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
76
|
+
--border: oklch(0.922 0 0);
|
|
77
|
+
--input: oklch(0.922 0 0);
|
|
78
|
+
--ring: oklch(0.708 0 0);
|
|
79
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
80
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
81
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
82
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
83
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
84
|
+
--sidebar: oklch(0.985 0 0);
|
|
85
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
86
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
87
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
88
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
89
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
90
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
91
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.dark {
|
|
95
|
+
--background: oklch(0.145 0 0);
|
|
96
|
+
--foreground: oklch(0.985 0 0);
|
|
97
|
+
--card: oklch(0.205 0 0);
|
|
98
|
+
--card-foreground: oklch(0.985 0 0);
|
|
99
|
+
--popover: oklch(0.205 0 0);
|
|
100
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
101
|
+
--primary: oklch(0.922 0 0);
|
|
102
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
103
|
+
--secondary: oklch(0.269 0 0);
|
|
104
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
105
|
+
--muted: oklch(0.269 0 0);
|
|
106
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
107
|
+
--accent: oklch(0.269 0 0);
|
|
108
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
109
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
110
|
+
--border: oklch(1 0 0 / 10%);
|
|
111
|
+
--input: oklch(1 0 0 / 15%);
|
|
112
|
+
--ring: oklch(0.556 0 0);
|
|
113
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
114
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
115
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
116
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
117
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
118
|
+
--sidebar: oklch(0.205 0 0);
|
|
119
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
120
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
121
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
122
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
123
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
124
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
125
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@layer base {
|
|
129
|
+
* {
|
|
130
|
+
@apply border-border outline-ring/50;
|
|
131
|
+
}
|
|
132
|
+
body {
|
|
133
|
+
@apply bg-background text-foreground;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File upload type definitions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type UploadState =
|
|
6
|
+
| "idle"
|
|
7
|
+
| "loading_config"
|
|
8
|
+
| "uploading"
|
|
9
|
+
| "creating_record"
|
|
10
|
+
| "success"
|
|
11
|
+
| "error"
|
|
12
|
+
| "cancelled";
|
|
13
|
+
|
|
14
|
+
export interface FileUploadItem {
|
|
15
|
+
file: File;
|
|
16
|
+
state: UploadState;
|
|
17
|
+
progress: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
contentVersionId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UploadedFile {
|
|
23
|
+
name: string;
|
|
24
|
+
size: number;
|
|
25
|
+
contentVersionId?: string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File upload utility functions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { UploadState } from "../types/fileUpload";
|
|
6
|
+
|
|
7
|
+
/** Maximum allowed file size: 2 GB */
|
|
8
|
+
export const MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
export function isFileTooLarge(file: File): boolean {
|
|
11
|
+
return file.size > MAX_FILE_SIZE_BYTES;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const UPLOADING_STATES: UploadState[] = ["loading_config", "uploading", "creating_record"];
|
|
15
|
+
|
|
16
|
+
export function isUploading(state: UploadState): boolean {
|
|
17
|
+
return UPLOADING_STATES.includes(state);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatFileSize(bytes: number): string {
|
|
21
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
22
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
23
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
24
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getFileExtension(name: string): string {
|
|
28
|
+
const lastDot = name.lastIndexOf(".");
|
|
29
|
+
return lastDot > 0 ? name.slice(lastDot + 1).toUpperCase() : "FILE";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatUploadSummary(successCount: number, totalCount: number): string {
|
|
33
|
+
const noun = totalCount === 1 ? "file" : "files";
|
|
34
|
+
if (totalCount === 1) {
|
|
35
|
+
return successCount === 1 ? "1 of 1 file uploaded" : "1 file uploading";
|
|
36
|
+
}
|
|
37
|
+
return `${successCount} of ${totalCount} ${noun} uploaded`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getProgressWidth(state: UploadState, progress: number): number {
|
|
41
|
+
if (state === "success") return 100;
|
|
42
|
+
if (state === "error" || state === "cancelled") return 0;
|
|
43
|
+
return progress;
|
|
44
|
+
}
|
package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/utils/labels.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** User-visible text and accessibility labels. */
|
|
2
|
+
export const LABELS = {
|
|
3
|
+
attach: "Attach",
|
|
4
|
+
uploadFiles: "Upload Files",
|
|
5
|
+
dropFilesHere: "Drop files here",
|
|
6
|
+
orDropFiles: "Or drop files",
|
|
7
|
+
uploadFilesDialogTitle: "Upload Files",
|
|
8
|
+
done: "Done",
|
|
9
|
+
cancelled: "Cancelled",
|
|
10
|
+
dropZone: "Upload files by clicking or dragging files here",
|
|
11
|
+
cancelUpload: (fileName: string) => `Cancel upload of ${fileName}`,
|
|
12
|
+
uploadComplete: "Upload complete",
|
|
13
|
+
uploadFailed: "Upload failed",
|
|
14
|
+
uploadStatus: "Upload status",
|
|
15
|
+
uploadedFiles: "Uploaded files",
|
|
16
|
+
uploadProgress: (fileName: string) => `Upload progress for ${fileName}`,
|
|
17
|
+
fileName: (name: string) => `File name: ${name}`,
|
|
18
|
+
fileSize: (size: string) => `File size: ${size}`,
|
|
19
|
+
fileTooLarge: (maxSize: string) => `File exceeds maximum size of ${maxSize}`,
|
|
20
|
+
doneButton: "Close dialog and finish upload",
|
|
21
|
+
} as const;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
|
|
23
|
+
/* Path mapping */
|
|
24
|
+
"baseUrl": ".",
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": ["./src/*"],
|
|
27
|
+
"@api/*": ["./src/api/*"],
|
|
28
|
+
"@components/*": ["./src/components/*"],
|
|
29
|
+
"@utils/*": ["./src/utils/*"],
|
|
30
|
+
"@styles/*": ["./src/styles/*"],
|
|
31
|
+
"@assets/*": ["./src/assets/*"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"include": ["src", "vite-env.d.ts", "vitest-env.d.ts"],
|
|
35
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
36
|
+
}
|
package/dist/force-app/main/default/webapplications/feature-react-file-upload/tsconfig.node.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
5
|
+
"skipLibCheck": true,
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"outDir": "./build"
|
|
11
|
+
},
|
|
12
|
+
"include": ["vite.config.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
6
|
+
import salesforce from "@salesforce/vite-plugin-webapp-experimental";
|
|
7
|
+
|
|
8
|
+
export default defineConfig(({ mode }) => {
|
|
9
|
+
return {
|
|
10
|
+
plugins: [tailwindcss(), react(), salesforce({ debug: true })],
|
|
11
|
+
|
|
12
|
+
build: {
|
|
13
|
+
outDir: resolve(__dirname, "dist"),
|
|
14
|
+
assetsDir: "assets",
|
|
15
|
+
sourcemap: false,
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
resolve: {
|
|
19
|
+
dedupe: ["react", "react-dom"],
|
|
20
|
+
alias: {
|
|
21
|
+
react: path.resolve(__dirname, "node_modules/react"),
|
|
22
|
+
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
|
|
23
|
+
"@": path.resolve(__dirname, "./src"),
|
|
24
|
+
"@api": path.resolve(__dirname, "./src/api"),
|
|
25
|
+
"@components": path.resolve(__dirname, "./src/components"),
|
|
26
|
+
"@utils": path.resolve(__dirname, "./src/utils"),
|
|
27
|
+
"@styles": path.resolve(__dirname, "./src/styles"),
|
|
28
|
+
"@assets": path.resolve(__dirname, "./src/assets"),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
test: {
|
|
33
|
+
root: resolve(__dirname),
|
|
34
|
+
environment: "jsdom",
|
|
35
|
+
include: [
|
|
36
|
+
"src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
|
|
37
|
+
"src/**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
|
|
38
|
+
],
|
|
39
|
+
testTimeout: 10000,
|
|
40
|
+
globals: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
|
|
3
|
+
"version": "1.55.0",
|
|
4
|
+
"description": "Base SFDX project template",
|
|
5
|
+
"private": true,
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "echo 'No build required for base-sfdx-project'",
|
|
11
|
+
"clean": "echo 'No clean required for base-sfdx-project'",
|
|
12
|
+
"lint": "eslint **/{aura,lwc}/**/*.js",
|
|
13
|
+
"test": "npm run test:unit",
|
|
14
|
+
"test:coverage": "npm run test",
|
|
15
|
+
"test:unit": "sfdx-lwc-jest -- --passWithNoTests",
|
|
16
|
+
"test:unit:watch": "sfdx-lwc-jest --watch",
|
|
17
|
+
"test:unit:debug": "sfdx-lwc-jest --debug",
|
|
18
|
+
"test:unit:coverage": "sfdx-lwc-jest --coverage",
|
|
19
|
+
"prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
|
|
20
|
+
"prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
|
|
21
|
+
"precommit": "lint-staged"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@lwc/eslint-plugin-lwc": "^2.0.0",
|
|
25
|
+
"@prettier/plugin-xml": "^3.2.2",
|
|
26
|
+
"@salesforce/eslint-config-lwc": "^3.2.3",
|
|
27
|
+
"@salesforce/eslint-plugin-aura": "^2.0.0",
|
|
28
|
+
"@salesforce/eslint-plugin-lightning": "^1.0.0",
|
|
29
|
+
"@salesforce/sfdx-lwc-jest": "^7.0.1",
|
|
30
|
+
"eslint": "8.57.1",
|
|
31
|
+
"eslint-plugin-import": "^2.25.4",
|
|
32
|
+
"eslint-plugin-jest": "^28.8.1",
|
|
33
|
+
"husky": "^9.1.5",
|
|
34
|
+
"lint-staged": "^15.1.0",
|
|
35
|
+
"prettier": "^3.1.0",
|
|
36
|
+
"prettier-plugin-apex": "^2.0.1"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Use .apex files to store anonymous Apex.
|
|
2
|
+
// You can execute anonymous Apex in VS Code by selecting the
|
|
3
|
+
// apex text and running the command:
|
|
4
|
+
// SFDX: Execute Anonymous Apex with Currently Selected Text
|
|
5
|
+
// You can also execute the entire file by running the command:
|
|
6
|
+
// SFDX: Execute Anonymous Apex with Editor Contents
|
|
7
|
+
|
|
8
|
+
string tempvar = 'Enter_your_name_here';
|
|
9
|
+
System.debug('Hello World!');
|
|
10
|
+
System.debug('My name is ' + tempvar);
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@salesforce/webapp-template-feature-react-file-upload-experimental",
|
|
3
|
+
"version": "1.55.0",
|
|
4
|
+
"description": "File upload feature with a component to upload files to core",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"author": "",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts",
|
|
9
|
+
"types": "src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts",
|
|
13
|
+
"import": "./src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts",
|
|
14
|
+
"default": "./src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"clean": "rm -rf dist"
|
|
26
|
+
},
|
|
27
|
+
"nx": {
|
|
28
|
+
"targets": {
|
|
29
|
+
"build": {
|
|
30
|
+
"executor": "@salesforce/webapp-template-cli-experimental:apply-patches"
|
|
31
|
+
},
|
|
32
|
+
"watch": {
|
|
33
|
+
"executor": "@salesforce/webapp-template-cli-experimental:watch-patches"
|
|
34
|
+
},
|
|
35
|
+
"build:dist-app": {
|
|
36
|
+
"executor": "@salesforce/webapp-template-cli-experimental:build-dist-app"
|
|
37
|
+
},
|
|
38
|
+
"start": {
|
|
39
|
+
"executor": "@salesforce/webapp-template-cli-experimental:dev-server"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@salesforce/webapp-experimental": "^1.55.0",
|
|
45
|
+
"@types/react": "^19.2.7",
|
|
46
|
+
"@types/react-dom": "^19.2.3",
|
|
47
|
+
"nodemon": "^3.1.0",
|
|
48
|
+
"react-dom": "^19.2.1",
|
|
49
|
+
"react-router": "^7.10.1",
|
|
50
|
+
"vite": "^7.3.1"
|
|
51
|
+
},
|
|
52
|
+
"gitHead": "1c1e2f3f2de3f9ef18a3efec5f477775ccf72d31"
|
|
53
|
+
}
|
package/src/force-app/main/default/webapplications/feature-react-file-upload/src/api/fileUpload.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File upload API – config, upload, and ContentVersion creation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createRecord, getCurrentUser } from "@salesforce/webapp-experimental/api";
|
|
6
|
+
import { createDataSDK, type DataSDK } from "@salesforce/sdk-data";
|
|
7
|
+
|
|
8
|
+
// Lazy-initialized SDK instance
|
|
9
|
+
let sdkInstance: DataSDK | null = null;
|
|
10
|
+
async function getSDK() {
|
|
11
|
+
if (!sdkInstance) {
|
|
12
|
+
sdkInstance = await createDataSDK();
|
|
13
|
+
}
|
|
14
|
+
return sdkInstance;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare const __SF_API_VERSION__: string;
|
|
18
|
+
|
|
19
|
+
export interface UploadConfig {
|
|
20
|
+
token: string;
|
|
21
|
+
uploadUrl: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get upload config (token, uploadUrl) from /connect/file/upload/config.
|
|
26
|
+
*/
|
|
27
|
+
export async function getUploadConfig(): Promise<UploadConfig> {
|
|
28
|
+
const sdk = await getSDK();
|
|
29
|
+
if (!sdk?.fetch) {
|
|
30
|
+
throw new Error("Failed to initialize SDK");
|
|
31
|
+
}
|
|
32
|
+
const API_VERSION = __SF_API_VERSION__ || "65.0";
|
|
33
|
+
const configRes = await sdk.fetch(`/services/data/v${API_VERSION}/connect/file/upload/config`, {
|
|
34
|
+
method: "GET",
|
|
35
|
+
});
|
|
36
|
+
if (!configRes.ok) {
|
|
37
|
+
throw new Error(`Failed to get upload config: ${configRes.status} ${configRes.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
const config = (await configRes.json()) as UploadConfig;
|
|
40
|
+
const { token, uploadUrl } = config;
|
|
41
|
+
if (!token || !uploadUrl) {
|
|
42
|
+
throw new Error("Invalid upload config: missing token or uploadUrl");
|
|
43
|
+
}
|
|
44
|
+
return { token, uploadUrl };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Upload file via XHR
|
|
49
|
+
* @param signal - Optional AbortSignal to cancel the upload
|
|
50
|
+
*/
|
|
51
|
+
export function uploadToUrl(
|
|
52
|
+
file: File,
|
|
53
|
+
token: string,
|
|
54
|
+
uploadUrl: string,
|
|
55
|
+
onProgress: (percent: number) => void,
|
|
56
|
+
signal?: AbortSignal,
|
|
57
|
+
): Promise<string> {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const xhr = new XMLHttpRequest();
|
|
60
|
+
const formData = new FormData();
|
|
61
|
+
formData.append("token", token);
|
|
62
|
+
formData.append("fromUITier", "true");
|
|
63
|
+
formData.append("target", "ContentVersion");
|
|
64
|
+
formData.append("origin", "LWC");
|
|
65
|
+
formData.append("file", file, file.name);
|
|
66
|
+
|
|
67
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
68
|
+
if (e.lengthComputable) {
|
|
69
|
+
onProgress(Math.round((e.loaded / e.total) * 100));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
xhr.addEventListener("load", () => {
|
|
74
|
+
if (xhr.status < 200 || xhr.status >= 300) {
|
|
75
|
+
reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const data = parseJsonResponse(xhr.responseText);
|
|
80
|
+
const id =
|
|
81
|
+
(data.content_body_id as string | undefined) ??
|
|
82
|
+
(data.contentBodyId as string | undefined);
|
|
83
|
+
if (!id) {
|
|
84
|
+
reject(new Error("Response missing content_body_id"));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
resolve(id);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
xhr.addEventListener("error", () => reject(new Error("Network error during upload")));
|
|
94
|
+
xhr.addEventListener("abort", () => reject(new Error("Upload aborted")));
|
|
95
|
+
|
|
96
|
+
if (signal) {
|
|
97
|
+
signal.addEventListener("abort", () => {
|
|
98
|
+
xhr.abort();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
xhr.open("POST", uploadUrl);
|
|
103
|
+
xhr.send(formData);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Strip "while(1);" or similar JSON prefix from LWC-style responses.
|
|
109
|
+
*/
|
|
110
|
+
function parseJsonResponse(text: string): Record<string, unknown> {
|
|
111
|
+
const trimmed = text.trim();
|
|
112
|
+
const prefix = "while(1);";
|
|
113
|
+
const jsonStr = trimmed.startsWith(prefix) ? trimmed.slice(prefix.length).trim() : trimmed;
|
|
114
|
+
return JSON.parse(jsonStr) as Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create ContentVersion record via ui-api createRecord.
|
|
119
|
+
*/
|
|
120
|
+
export async function createContentVersion(
|
|
121
|
+
file: File,
|
|
122
|
+
contentBodyId: string,
|
|
123
|
+
userId: string,
|
|
124
|
+
): Promise<string | undefined> {
|
|
125
|
+
const fields = {
|
|
126
|
+
FirstPublishLocationId: userId,
|
|
127
|
+
Title: fileNameWithoutExtension(file.name),
|
|
128
|
+
PathOnClient: file.name,
|
|
129
|
+
ContentBodyId: contentBodyId,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const response = await createRecord("ContentVersion", fields);
|
|
133
|
+
const id = (response as { id?: string }).id ?? response.fields?.Id?.value;
|
|
134
|
+
return id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get file name without extension.
|
|
139
|
+
*/
|
|
140
|
+
function fileNameWithoutExtension(name: string): string {
|
|
141
|
+
const lastDot = name.lastIndexOf(".");
|
|
142
|
+
return lastDot > 0 ? name.slice(0, lastDot) : name;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get current user Id (for FirstPublishLocationId).
|
|
147
|
+
*/
|
|
148
|
+
export async function getCurrentUserId(): Promise<string> {
|
|
149
|
+
const user = await getCurrentUser();
|
|
150
|
+
if (!user?.id) {
|
|
151
|
+
throw new Error("Current user not found");
|
|
152
|
+
}
|
|
153
|
+
return user.id;
|
|
154
|
+
}
|