# 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.buzzy.buzz/the-building-blocks/code-widget-custom-code/examples/file-manager.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
