create-cloudinary-react 1.0.0-beta.1
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/.github/CODEOWNERS +4 -0
- package/.github/workflows/release.yml +45 -0
- package/.husky/commit-msg +1 -0
- package/.releaserc.json +23 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/cli.js +283 -0
- package/commitlint.config.js +23 -0
- package/package.json +46 -0
- package/templates/.cursor/mcp.json.template +10 -0
- package/templates/.cursorrules.template +525 -0
- package/templates/.env.example.template +10 -0
- package/templates/.env.template +5 -0
- package/templates/.gitignore.template +29 -0
- package/templates/README.md.template +46 -0
- package/templates/eslint.config.js.template +23 -0
- package/templates/index.html.template +13 -0
- package/templates/package.json.template +32 -0
- package/templates/src/App.css.template +94 -0
- package/templates/src/App.tsx.template +80 -0
- package/templates/src/cloudinary/UploadWidget.tsx.template +112 -0
- package/templates/src/cloudinary/config.ts.template +21 -0
- package/templates/src/index.css.template +29 -0
- package/templates/src/main.tsx.template +10 -0
- package/templates/tsconfig.app.json.template +24 -0
- package/templates/tsconfig.json.template +7 -0
- package/templates/tsconfig.node.json.template +22 -0
- package/templates/vite.config.ts.template +7 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
.app {
|
|
2
|
+
min-height: 100vh;
|
|
3
|
+
padding: 2rem;
|
|
4
|
+
max-width: 1200px;
|
|
5
|
+
margin: 0 auto;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.main-content {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 2rem;
|
|
12
|
+
align-items: center;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h1 {
|
|
16
|
+
font-size: 2.5rem;
|
|
17
|
+
margin: 0;
|
|
18
|
+
text-align: center;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
h2 {
|
|
22
|
+
font-size: 1.5rem;
|
|
23
|
+
margin: 0 0 1rem 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.upload-section,
|
|
27
|
+
.image-section {
|
|
28
|
+
width: 100%;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
align-items: center;
|
|
32
|
+
padding: 2rem;
|
|
33
|
+
background: rgba(255, 255, 255, 0.05);
|
|
34
|
+
border-radius: 1rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.display-image {
|
|
38
|
+
max-width: 100%;
|
|
39
|
+
height: auto;
|
|
40
|
+
border-radius: 0.5rem;
|
|
41
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.image-info {
|
|
45
|
+
margin-top: 1rem;
|
|
46
|
+
font-size: 0.875rem;
|
|
47
|
+
color: rgba(255, 255, 255, 0.7);
|
|
48
|
+
font-family: monospace;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ai-prompts-section {
|
|
52
|
+
width: 100%;
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
align-items: center;
|
|
56
|
+
padding: 2rem;
|
|
57
|
+
background: rgba(99, 102, 241, 0.1);
|
|
58
|
+
border-radius: 1rem;
|
|
59
|
+
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.prompts-intro {
|
|
63
|
+
margin: 0.5rem 0 1rem 0;
|
|
64
|
+
color: rgba(255, 255, 255, 0.8);
|
|
65
|
+
text-align: center;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.prompts-list {
|
|
69
|
+
list-style: none;
|
|
70
|
+
padding: 0;
|
|
71
|
+
margin: 0;
|
|
72
|
+
width: 100%;
|
|
73
|
+
max-width: 600px;
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
gap: 0.75rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.prompts-list li {
|
|
80
|
+
padding: 0.75rem 1rem;
|
|
81
|
+
background: rgba(255, 255, 255, 0.05);
|
|
82
|
+
border-radius: 0.5rem;
|
|
83
|
+
border-left: 3px solid rgba(99, 102, 241, 0.5);
|
|
84
|
+
color: rgba(255, 255, 255, 0.9);
|
|
85
|
+
font-size: 0.9rem;
|
|
86
|
+
transition: all 0.2s;
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.prompts-list li:hover {
|
|
91
|
+
background: rgba(255, 255, 255, 0.1);
|
|
92
|
+
border-left-color: rgba(99, 102, 241, 0.8);
|
|
93
|
+
transform: translateX(4px);
|
|
94
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { AdvancedImage, placeholder, lazyload } from '@cloudinary/react';
|
|
3
|
+
import { fill } from '@cloudinary/url-gen/actions/resize';
|
|
4
|
+
import { format, quality } from '@cloudinary/url-gen/actions/delivery';
|
|
5
|
+
import { auto } from '@cloudinary/url-gen/qualifiers/format';
|
|
6
|
+
import { auto as autoQuality } from '@cloudinary/url-gen/qualifiers/quality';
|
|
7
|
+
import { cld } from './cloudinary/config';
|
|
8
|
+
import { UploadWidget } from './cloudinary/UploadWidget';
|
|
9
|
+
import './App.css';
|
|
10
|
+
|
|
11
|
+
function App() {
|
|
12
|
+
const [uploadedImageId, setUploadedImageId] = useState<string | null>(null);
|
|
13
|
+
|
|
14
|
+
const handleUploadSuccess = (result: any) => {
|
|
15
|
+
console.log('Upload successful:', result);
|
|
16
|
+
setUploadedImageId(result.public_id);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const handleUploadError = (error: Error) => {
|
|
20
|
+
console.error('Upload error:', error);
|
|
21
|
+
alert(`Upload failed: ${error.message}`);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Display uploaded image if available, otherwise show a sample
|
|
25
|
+
const imageId = uploadedImageId || 'samples/cloudinary-icon';
|
|
26
|
+
|
|
27
|
+
const displayImage = cld
|
|
28
|
+
.image(imageId)
|
|
29
|
+
.resize(fill().width(600).height(400))
|
|
30
|
+
.delivery(format(auto()))
|
|
31
|
+
.delivery(quality(autoQuality()));
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="app">
|
|
35
|
+
<main className="main-content">
|
|
36
|
+
<h1>Cloudinary React + Vite Boilerplate</h1>
|
|
37
|
+
<p>This is a ready-to-use development environment with Cloudinary integration.</p>
|
|
38
|
+
|
|
39
|
+
<div className="upload-section">
|
|
40
|
+
<h2>Upload an Image</h2>
|
|
41
|
+
<UploadWidget
|
|
42
|
+
onUploadSuccess={handleUploadSuccess}
|
|
43
|
+
onUploadError={handleUploadError}
|
|
44
|
+
buttonText="Upload Image"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div className="image-section">
|
|
49
|
+
<h2>Display Image</h2>
|
|
50
|
+
<AdvancedImage
|
|
51
|
+
cldImg={displayImage}
|
|
52
|
+
plugins={[placeholder({ mode: 'blur' }), lazyload()]}
|
|
53
|
+
alt={uploadedImageId ? 'Your uploaded image' : 'Sample image'}
|
|
54
|
+
className="display-image"
|
|
55
|
+
/>
|
|
56
|
+
{uploadedImageId && (
|
|
57
|
+
<p className="image-info">Public ID: {uploadedImageId}</p>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div className="ai-prompts-section">
|
|
62
|
+
<h2>🤖 Try Asking Your AI Assistant</h2>
|
|
63
|
+
<p className="prompts-intro">Here are some Cloudinary-related tasks you can try:</p>
|
|
64
|
+
<ul className="prompts-list">
|
|
65
|
+
<li>"Add responsive image transformations with breakpoints"</li>
|
|
66
|
+
<li>"Create a video player component with Cloudinary"</li>
|
|
67
|
+
<li>"Add image effects like blur, grayscale, or sepia"</li>
|
|
68
|
+
<li>"Implement automatic image cropping with face detection"</li>
|
|
69
|
+
<li>"Add image overlays with text or logos"</li>
|
|
70
|
+
<li>"Create an image gallery with lazy loading"</li>
|
|
71
|
+
<li>"Add image optimization with format and quality auto"</li>
|
|
72
|
+
<li>"Implement image transformations based on screen size"</li>
|
|
73
|
+
</ul>
|
|
74
|
+
</div>
|
|
75
|
+
</main>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default App;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { uploadPreset } from './config';
|
|
3
|
+
|
|
4
|
+
interface UploadWidgetProps {
|
|
5
|
+
onUploadSuccess?: (result: any) => void;
|
|
6
|
+
onUploadError?: (error: Error) => void;
|
|
7
|
+
buttonText?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface Window {
|
|
13
|
+
cloudinary: any;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function UploadWidget({
|
|
18
|
+
onUploadSuccess,
|
|
19
|
+
onUploadError,
|
|
20
|
+
buttonText = 'Upload Image',
|
|
21
|
+
className = '',
|
|
22
|
+
}: UploadWidgetProps) {
|
|
23
|
+
const widgetRef = useRef<any>(null);
|
|
24
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
// Load Cloudinary Upload Widget script
|
|
28
|
+
if (!window.cloudinary) {
|
|
29
|
+
const script = document.createElement('script');
|
|
30
|
+
script.src = 'https://upload-widget.cloudinary.com/global/all.js';
|
|
31
|
+
script.async = true;
|
|
32
|
+
document.body.appendChild(script);
|
|
33
|
+
|
|
34
|
+
script.onload = () => {
|
|
35
|
+
initializeWidget();
|
|
36
|
+
};
|
|
37
|
+
} else {
|
|
38
|
+
initializeWidget();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function initializeWidget() {
|
|
42
|
+
if (!window.cloudinary || !buttonRef.current) return;
|
|
43
|
+
|
|
44
|
+
if (!uploadPreset) {
|
|
45
|
+
console.warn(
|
|
46
|
+
'VITE_CLOUDINARY_UPLOAD_PRESET is not set. ' +
|
|
47
|
+
'Create an unsigned upload preset in your Cloudinary dashboard.'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
widgetRef.current = window.cloudinary.createUploadWidget(
|
|
52
|
+
{
|
|
53
|
+
cloudName: import.meta.env.VITE_CLOUDINARY_CLOUD_NAME,
|
|
54
|
+
uploadPreset: uploadPreset || undefined,
|
|
55
|
+
sources: ['local', 'camera', 'url'],
|
|
56
|
+
multiple: false,
|
|
57
|
+
},
|
|
58
|
+
(error: any, result: any) => {
|
|
59
|
+
if (error) {
|
|
60
|
+
console.error('Upload error:', error);
|
|
61
|
+
onUploadError?.(new Error(error.message || 'Upload failed'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (result && result.event === 'success') {
|
|
66
|
+
console.log('Upload success:', result.info);
|
|
67
|
+
onUploadSuccess?.(result.info);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
buttonRef.current.addEventListener('click', () => {
|
|
73
|
+
widgetRef.current?.open();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return () => {
|
|
78
|
+
if (buttonRef.current && widgetRef.current) {
|
|
79
|
+
buttonRef.current.removeEventListener('click', () => {
|
|
80
|
+
widgetRef.current?.open();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, [onUploadSuccess, onUploadError]);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<button
|
|
88
|
+
ref={buttonRef}
|
|
89
|
+
type="button"
|
|
90
|
+
className={className}
|
|
91
|
+
style={{
|
|
92
|
+
padding: '0.75rem 1.5rem',
|
|
93
|
+
fontSize: '1rem',
|
|
94
|
+
fontWeight: 500,
|
|
95
|
+
color: 'white',
|
|
96
|
+
backgroundColor: '#6366f1',
|
|
97
|
+
border: 'none',
|
|
98
|
+
borderRadius: '0.5rem',
|
|
99
|
+
cursor: 'pointer',
|
|
100
|
+
transition: 'background-color 0.2s',
|
|
101
|
+
}}
|
|
102
|
+
onMouseEnter={(e) => {
|
|
103
|
+
e.currentTarget.style.backgroundColor = '#4f46e5';
|
|
104
|
+
}}
|
|
105
|
+
onMouseLeave={(e) => {
|
|
106
|
+
e.currentTarget.style.backgroundColor = '#6366f1';
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{buttonText}
|
|
110
|
+
</button>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Cloudinary } from '@cloudinary/url-gen';
|
|
2
|
+
|
|
3
|
+
// Get environment variables - Vite requires VITE_ prefix
|
|
4
|
+
const cloudName = import.meta.env.VITE_CLOUDINARY_CLOUD_NAME;
|
|
5
|
+
|
|
6
|
+
if (!cloudName) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
'VITE_CLOUDINARY_CLOUD_NAME is not set. Please create a .env file with your Cloudinary cloud name.\n' +
|
|
9
|
+
'See .env.example for reference.'
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Create and export Cloudinary instance
|
|
14
|
+
export const cld = new Cloudinary({
|
|
15
|
+
cloud: {
|
|
16
|
+
cloudName: cloudName,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Export upload preset for convenience
|
|
21
|
+
export const uploadPreset = import.meta.env.VITE_CLOUDINARY_UPLOAD_PRESET || '';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
font-weight: 400;
|
|
5
|
+
|
|
6
|
+
color-scheme: light dark;
|
|
7
|
+
color: rgba(255, 255, 255, 0.87);
|
|
8
|
+
background-color: #242424;
|
|
9
|
+
|
|
10
|
+
font-synthesis: none;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
* {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
margin: 0;
|
|
22
|
+
min-width: 320px;
|
|
23
|
+
min-height: 100vh;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#root {
|
|
27
|
+
width: 100%;
|
|
28
|
+
min-height: 100vh;
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"types": ["vite/client"],
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"erasableSyntaxOnly": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedSideEffectImports": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"]
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"erasableSyntaxOnly": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"noUncheckedSideEffectImports": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["vite.config.ts"]
|
|
22
|
+
}
|