> For the complete documentation index, see [llms.txt](https://docs.buzzy.buzz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.buzzy.buzz/the-building-blocks/code-widget-custom-code/examples/file-manager.md).

# File Manager

This example demonstrates a comprehensive file management system with upload, download, and organization capabilities using the BuzzyFrameAPI.

## Key Features

* **Universal file upload** supporting all file types
* **Drag-and-drop interface** with visual feedback
* **File metadata display** including size, type, and filename
* **Download functionality** with direct file access
* **File deletion** with confirmation dialogs
* **Progress tracking** during upload operations
* **Automatic URL refresh** for expired S3 links
* **Responsive design** with mobile-friendly layout

## Use Cases

Ideal for applications requiring:

* Document management systems
* File sharing platforms
* Project collaboration tools
* Digital asset management
* Client portals with file exchange
* Administrative dashboards

## Implementation Overview

This widget follows the **Child Table Data** pattern, storing files as child records linked to a parent row. Key implementation features:

* Support for any file type (documents, images, videos, etc.)
* Efficient file size formatting and display
* S3 integration with presigned URLs for security
* Real-time synchronization with backend data
* Mobile-responsive design using Bulma CSS

## Code Example

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>File Manager</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script
      src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js"
      crossorigin
    ></script>
    <style>
      .dropzone {
        border: 2px dashed #ccc;
        cursor: pointer;
        transition: all 0.3s ease;
      }
      .dropzone.active {
        border-color: #4caf50;
        background-color: #f8fff8;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel" data-presets="env,react">
      const buzzyFrameAPI = new BuzzyFrameAPI();
      let appInitData = null;
      let appFiles = [];

      async function initApp() {
        appInitData = await buzzyFrameAPI.initialise();
        await fetchAndUpdateFiles();

        buzzyFrameAPI.addMicroappListener({
          microAppID: appInitData.rowJSON._id,
          listener: async () => {
            console.log('Child data updated, refreshing files');
            await fetchAndUpdateFiles();
          },
        });

        setInterval(() => {
          refreshExpiredUrls();
        }, 5 * 60 * 1000);
      }

      async function fetchAndUpdateFiles() {
        try {
          const { rowJSON = {} } = appInitData || {};
          const children = await buzzyFrameAPI.getChildItemsByField({
            appID: rowJSON._id,
            fieldID: rowJSON.fieldID,
          });

          appFiles = children;

          if (window.updateFiles) {
            window.updateFiles(appFiles);
          }
        } catch (error) {
          console.error('Error fetching files:', error);
          if (window.updateFiles) {
            window.updateFiles([]);
          }
        }
      }

      function App() {
        const { useEffect, useState, useCallback } = React;
        const [files, setFiles] = useState([]);
        const [loading, setLoading] = useState(true);
        const [uploading, setUploading] = useState(false);
        const [progress, setProgress] = useState(0);
        const [dragActive, setDragActive] = useState(false);

        useEffect(() => {
          window.updateFiles = newFiles => {
            setFiles(newFiles);
            setLoading(false);
          };

          if (appFiles.length > 0) {
            setFiles(appFiles);
            setLoading(false);
          }

          return () => {
            window.updateFiles = null;
          };
        }, []);

        const refreshExpiredUrls = async () => {
          const updatedFiles = await Promise.all(
            appFiles.map(async file => {
              if (
                file.content.expiredAt &&
                Date.now() > file.content.expiredAt
              ) {
                try {
                  const { presignedReadUrl } =
                    await buzzyFrameAPI.createPresignedPutAndReadUrls(
                      appInitData.rowJSON._id,
                      file.content.filename,
                    );

                  await buzzyFrameAPI.updateMicroappChild({
                    childID: file._id,
                    content: {
                      ...file.content,
                      url: presignedReadUrl,
                      expiredAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
                    },
                  });

                  return {
                    ...file,
                    content: {
                      ...file.content,
                      url: presignedReadUrl,
                    },
                  };
                } catch (error) {
                  console.error('Error refreshing URL:', error);
                  return file;
                }
              }
              return file;
            }),
          );
          appFiles = updatedFiles;
          if (window.updateFiles) {
            window.updateFiles(appFiles);
          }
        };

        const handleDrop = useCallback(async e => {
          e.preventDefault();
          setDragActive(false);

          const files = e.dataTransfer?.files;
          if (!files?.length) return;

          setUploading(true);
          setProgress(0);

          try {
            const { rowJSON } = appInitData;

            for (let i = 0; i < files.length; i++) {
              const file = files[i];
              setProgress((i / files.length) * 100);

              const { presignedPutUrl, presignedReadUrl } =
                await buzzyFrameAPI.createPresignedPutAndReadUrls(
                  rowJSON._id,
                  file.name,
                );

              await buzzyFrameAPI.uploadFileToS3({
                presignedPutUrl,
                file,
                onProgress: percent =>
                  setProgress(((i + percent / 100) / files.length) * 100),
              });

              await buzzyFrameAPI.createMicroappChild({
                microAppResourceID: rowJSON.parentResourceID,
                appID: rowJSON._id,
                fieldID: rowJSON.fieldID,
                content: {
                  url: presignedReadUrl,
                  filename: file.name,
                  size: file.size,
                  type: file.type,
                  expiredAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
                },
              });
            }
          } catch (error) {
            console.error('Error uploading files:', error);
          } finally {
            setUploading(false);
            setProgress(0);
          }
        }, []);

        const handleDeleteFile = async childID => {
          if (!confirm('Are you sure you want to delete this file?')) return;

          try {
            await buzzyFrameAPI.removeMicroappChild({ childID });
          } catch (error) {
            console.error('Error deleting file:', error);
          }
        };

        const formatFileSize = bytes => {
          const units = ['B', 'KB', 'MB', 'GB'];
          let size = bytes;
          let unitIndex = 0;

          while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
          }

          return size.toFixed(1) + ' ' + units[unitIndex];
        };

        if (loading)
          return <div className="has-text-centered p-4">Loading...</div>;

        return (
          <div className="container">
            <div
              className={`dropzone box has-text-centered p-5 mb-4 ${
                dragActive ? 'active' : ''
              }`}
              onDragOver={e => {
                e.preventDefault();
                setDragActive(true);
              }}
              onDragLeave={() => setDragActive(false)}
              onDrop={handleDrop}>
              <p className="mb-3">
                Drag and drop files here or click to browse
              </p>
              <input
                type="file"
                multiple
                onChange={e =>
                  handleDrop({ dataTransfer: { files: e.target.files } })
                }
                style={{ display: 'none' }}
                id="file-input"
              />
              <button
                className="button is-primary"
                onClick={() => document.getElementById('file-input').click()}>
                Browse Files
              </button>
            </div>

            {uploading && (
              <div className="mb-4">
                <progress
                  className="progress is-primary"
                  value={progress}
                  max="100">
                  {progress}%
                </progress>
              </div>
            )}

            <div className="content">
              {files.map(file => (
                <div key={file._id} className="box">
                  <div className="columns is-mobile is-vcentered">
                    <div className="column">
                      <div>
                        <strong>{file.content.filename}</strong>
                      </div>
                      <div className="has-text-grey is-size-7">
                        {formatFileSize(file.content.size)} •{' '}
                        {file.content.type}
                      </div>
                    </div>
                    <div className="column is-narrow">
                      <div className="buttons has-addons">
                        <a
                          href={file.content.url}
                          target="_blank"
                          rel="noopener noreferrer"
                          className="button is-primary is-small">
                          <span className="icon is-small">
                            <i className="fas fa-download"></i>
                          </span>
                          <span>Download</span>
                        </a>
                        <button
                          className="button is-danger is-small"
                          onClick={() => handleDeleteFile(file._id)}>
                          <span className="icon is-small">
                            <i className="fas fa-trash"></i>
                          </span>
                          <span>Delete</span>
                        </button>
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        );
      }

      initApp().then(() => {
        ReactDOM.render(<App />, document.getElementById('root'));
      });
    </script>
  </body>
</html>
```

## Key Concepts

### Universal File Support

* **All File Types**: Supports documents, images, videos, archives, and any other file format
* **File Metadata**: Displays filename, size, and MIME type for easy identification
* **Size Formatting**: Human-readable file size display (B, KB, MB, GB)

### File Operations

* **Upload**: Drag-and-drop or click-to-browse file selection
* **Download**: Direct file access through presigned URLs
* **Delete**: Secure file removal with confirmation
* **Progress Tracking**: Real-time upload progress feedback

### S3 Integration

* **Presigned URLs**: Secure file upload and access without exposing credentials
* **URL Expiration**: Automatic refresh of expired URLs to maintain access
* **Storage Management**: Efficient file storage with proper metadata tracking

### User Experience

* **Responsive Design**: Works seamlessly on desktop and mobile devices
* **Visual Feedback**: Clear indicators for drag-and-drop states and upload progress
* **Error Handling**: Graceful error handling with user-friendly messages

This file manager demonstrates how code widgets can provide enterprise-grade file management capabilities within Buzzy applications, suitable for any use case requiring file storage and organization.
