feat: redo settings and all org pages. (#601)
* feat: new components, deploy storybook * refactor: simplify component field definitions and enhance button loading state - Removed unnecessary type imports and simplified the FIELDS array definitions across multiple components in Storybook. - Updated button component to use a loading state instead of isLoading, improving clarity and consistency in props. - Adjusted button stories to reflect the new loading prop and enhanced the button's loading visual feedback. - Minor CSS adjustments for better layout and styling consistency. * chore: add field, card, password components * feat: improve page component & update AuthUI path * chore: update bits-ui to version 2.14.4 and all org related UI's - Updated bits-ui dependency in pnpm-lock.yaml and package.json to version 2.14.4. - Refactored imports for RoleBasedSecurity and UpgradeBanner components to use the new ui feature directory. - Removed unused Course components and related files to clean up the codebase. - Enhanced various components to improve consistency and maintainability. * chore: move all setting buttons to the top and update auth ui styles * chore: remove svelte-loading-spinners and replace with @cio/ui/base/spinner - Removed all instances of svelte-loading-spinners from the project. - Updated components to use the new Spinner from @cio/ui/base/spinner. - Cleaned up pnpm-lock.yaml and package.json to reflect the removal of svelte-loading-spinners. - Added new CSS animations for rocket launch effects in app.css. - Enhanced email change flow with updated verification messages in translations. * chore: remove supabase file upload from dashboard and move to r2 * chore: update organization API and UI components - Added new API methods for organization plan operations and team management. - Introduced middleware for API key authentication and organization admin checks. - Refactored organization-related components to utilize the new API structure. - Updated various UI components to improve consistency and maintainability. - Enhanced error handling and validation for organization-related actions. - Removed deprecated services and streamlined the codebase.
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
import 'dotenv/config';
|
||||
|
||||
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { join, relative } from 'path';
|
||||
import { readFileSync, readdirSync, statSync } from 'fs';
|
||||
|
||||
interface UploadOptions {
|
||||
key: string;
|
||||
content: Buffer | string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
class StorybookUploader {
|
||||
private s3Client: S3Client | null = null;
|
||||
private buildDir: string;
|
||||
private bucket: string;
|
||||
|
||||
constructor() {
|
||||
this.buildDir = join(process.cwd(), 'storybook-static');
|
||||
this.bucket = 'assets';
|
||||
this.initializeS3Client();
|
||||
}
|
||||
|
||||
private initializeS3Client() {
|
||||
const accessKey = process.env.CLOUDFLARE_ACCESS_KEY;
|
||||
const secretKey = process.env.CLOUDFLARE_SECRET_ACCESS_KEY;
|
||||
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||
|
||||
if (accessKey && secretKey && accountId) {
|
||||
this.s3Client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId: accessKey,
|
||||
secretAccessKey: secretKey
|
||||
}
|
||||
});
|
||||
console.log('✅ S3 client initialized for Cloudflare R2');
|
||||
} else {
|
||||
console.warn('⚠️ Cloudflare R2 credentials not found. Cannot upload to R2.');
|
||||
throw new Error('Missing Cloudflare R2 credentials');
|
||||
}
|
||||
}
|
||||
|
||||
private getContentType(filePath: string): string {
|
||||
const ext = filePath.split('.').pop()?.toLowerCase();
|
||||
const contentTypes: Record<string, string> = {
|
||||
html: 'text/html',
|
||||
js: 'application/javascript',
|
||||
json: 'application/json',
|
||||
css: 'text/css',
|
||||
png: 'image/png',
|
||||
jpg: 'image/jpeg',
|
||||
jpeg: 'image/jpeg',
|
||||
gif: 'image/gif',
|
||||
svg: 'image/svg+xml',
|
||||
ico: 'image/x-icon',
|
||||
woff: 'font/woff',
|
||||
woff2: 'font/woff2',
|
||||
ttf: 'font/ttf',
|
||||
eot: 'application/vnd.ms-fontobject',
|
||||
map: 'application/json',
|
||||
txt: 'text/plain',
|
||||
xml: 'application/xml'
|
||||
};
|
||||
return contentTypes[ext || ''] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
private getCacheControl(filePath: string): string {
|
||||
// Cache static assets longer, HTML files shorter
|
||||
if (filePath.endsWith('.html')) {
|
||||
return 'public, max-age=300'; // 5 minutes for HTML
|
||||
}
|
||||
if (filePath.match(/\.(js|css|woff|woff2|ttf|png|jpg|jpeg|gif|svg|ico)$/)) {
|
||||
return 'public, max-age=31536000, immutable'; // 1 year for assets
|
||||
}
|
||||
return 'public, max-age=3600'; // 1 hour default
|
||||
}
|
||||
|
||||
private getAllFiles(dir: string, baseDir: string = dir): string[] {
|
||||
const files: string[] = [];
|
||||
const entries = readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
const relativePath = relative(baseDir, fullPath);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...this.getAllFiles(fullPath, baseDir));
|
||||
} else {
|
||||
files.push(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
async uploadFile(filePath: string): Promise<void> {
|
||||
if (!this.s3Client) {
|
||||
throw new Error('S3 client not initialized. Check your Cloudflare R2 credentials.');
|
||||
}
|
||||
|
||||
try {
|
||||
const fullPath = join(this.buildDir, filePath);
|
||||
const content = readFileSync(fullPath);
|
||||
const contentType = this.getContentType(filePath);
|
||||
const cacheControl = this.getCacheControl(filePath);
|
||||
|
||||
// Upload to storybook/ directory in R2
|
||||
const key = `storybook/${filePath}`;
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
Body: content,
|
||||
ContentType: contentType,
|
||||
CacheControl: cacheControl
|
||||
});
|
||||
|
||||
await this.s3Client.send(command);
|
||||
console.log(`✅ Uploaded: ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error uploading ${filePath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async uploadAll(): Promise<void> {
|
||||
try {
|
||||
// Check if build directory exists
|
||||
try {
|
||||
statSync(this.buildDir);
|
||||
} catch {
|
||||
throw new Error(`Build directory not found: ${this.buildDir}. Please run 'pnpm build' first.`);
|
||||
}
|
||||
|
||||
console.log('🔄 Starting Storybook upload to R2...');
|
||||
console.log(`📁 Build directory: ${this.buildDir}`);
|
||||
|
||||
const files = this.getAllFiles(this.buildDir);
|
||||
console.log(`📦 Found ${files.length} files to upload`);
|
||||
|
||||
// Upload files in batches to avoid overwhelming the API
|
||||
const batchSize = 10;
|
||||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
const batch = files.slice(i, i + batchSize);
|
||||
await Promise.all(batch.map((file) => this.uploadFile(file)));
|
||||
console.log(`📤 Uploaded batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(files.length / batchSize)}`);
|
||||
}
|
||||
|
||||
console.log('🎉 Storybook upload completed successfully!');
|
||||
console.log(`📊 Total files uploaded: ${files.length}`);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to upload Storybook:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CLI interface
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(`
|
||||
Usage: tsx scripts/upload-storybook.ts
|
||||
|
||||
Examples:
|
||||
tsx scripts/upload-storybook.ts
|
||||
|
||||
Environment Variables Required for R2 Upload:
|
||||
CLOUDFLARE_ACCESS_KEY
|
||||
CLOUDFLARE_SECRET_ACCESS_KEY
|
||||
CLOUDFLARE_ACCOUNT_ID
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
const uploader = new StorybookUploader();
|
||||
await uploader.uploadAll();
|
||||
}
|
||||
|
||||
// Run the script if called directly
|
||||
if (process.argv[1] && process.argv[1].endsWith('upload-storybook.ts')) {
|
||||
main()
|
||||
.then(() => {
|
||||
console.log('Script completed successfully');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Script failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { StorybookUploader };
|
||||
Reference in New Issue
Block a user