logo
Hello World!

BetaHub Web Form

June 20, 2025 (Updated: July 5, 2025)
typescriptbetahubpackage

TypeScript library for creating external bug reporting forms with file upload, drag & drop, and real-time validation.

The project was created at the beginning of 2025 as a response to specific needs of the BetaHub platform in the area of collecting feedback from external testers. The problem was clear: development teams needed a simple way to integrate bug reporting forms in their applications, without having to implement complex validation logic, file upload, or API integration.

BetaHub Web Form is a TypeScript library that solves this problem by providing a ready-made, universal solution. The library offers two usage modes: imperative (through HTML attributes) and declarative (through programmatic API), making it accessible to both frontend developers and people without programming experience.

The library was designed with maximum universality in mind, offering two complementary integration methods:

Imperative mode (HTML):

  • Integration through data-bhwf-* attributes in HTML
  • Ideal for static pages or quick prototypes
  • Full functionality without programming

Programmatic mode (API):

  • Full TypeScript API with types
  • Maximum control over behavior
  • Integration with frameworks (React, Vue, Angular)
  • Advanced configuration and customization

  • Intelligent file upload: Automatic file type recognition and routing to appropriate endpoints
  • Drag & drop: Native drag and drop file handling with visual feedback
  • Real-time validation: Immediate validation with visual error messages
  • Responsive design: Ready-made styles for dark and light themes
  • Progress tracking: Visual tracking of upload progress
  • Error handling: Comprehensive error handling system
  • Accessibility: Full support for screen readers and keyboard navigation

TypeScript, Microbundle, Dropzone.js, HTML5 File API

The library was designed as a modular system where each functionality is implemented as an independent module. This architecture ensures:

  • Easy testing: Each module can be tested independently
  • Extensibility: New functionalities can be added without affecting existing code
  • Maintainability: Clear separation of responsibilities

typescript
// Library module structure
src/
├── bhwf.ts              // Main entry point
├── Form.ts              // Form class - main logic
├── api.ts               // BetaHub API communication
├── types.ts             // TypeScript type definitions
├── functions.ts         // Helper functions
├── inputs/
│   ├── Input.ts         // Text field handling
│   └── FileInput.ts     // File field handling
├── bhwf.css             // Main styles
└── dropzone.css         // Dropzone styles
// Library module structure
src/
├── bhwf.ts              // Main entry point
├── Form.ts              // Form class - main logic
├── api.ts               // BetaHub API communication
├── types.ts             // TypeScript type definitions
├── functions.ts         // Helper functions
├── inputs/
│   ├── Input.ts         // Text field handling
│   └── FileInput.ts     // File field handling
├── bhwf.css             // Main styles
└── dropzone.css         // Dropzone styles

The library uses microbundle to optimize the build process:

  • Multiple formats: ES modules, CommonJS, UMD
  • Tree shaking (code optimization): Automatic removal of unused code
  • TypeScript definitions: Automatic generation of .d.ts files
  • Minification: Size optimization for production

The library uses dropzone.js as the foundation for drag & drop functionality, but extends it with:

  • Custom event handling: Custom events for better integration
  • Progress tracking system integration: Synchronization with progress tracking system
  • Error handling: Extended error handling specific to BetaHub
  • Theme integration: Automatic adaptation to library themes

The simplest way to integrate - just add appropriate attributes to existing HTML structure:

html
<head>
  <script src="https://unpkg.com/betahub-web-form@1.*.*/dist/bhwf.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/betahub-web-form@1.*.*/dist/bhwf.min.css" />
  <link rel="stylesheet" href="https://unpkg.com/betahub-web-form@1.*.*/dist/dropzone.min.css" />
</head>

<form
  data-bhwf-form
  data-bhwf-project-id="pr-123"
  data-bhwf-api-key="tkn-abc"
>
  <h1>Submit your issue</h1>

  <label>Description</label>
  <div class="bhwf-input-container">
    <textarea
      data-bhwf-input="description"
      data-bhwf-counter
      placeholder="Describe your issue as detailed as possible"
    ></textarea>
  </div>
  <span data-bhwf-error-msg="description"></span>

  <label>Steps to Reproduce</label>
  <textarea
    data-bhwf-input="stepsToReproduce"
    placeholder="Describe the steps leading to the issue"
  ></textarea>

  <label>Attach Media</label>
  <input type="file" data-bhwf-input="media" data-bhwf-dropzone multiple />

  <div data-bhwf-button="submit">Submit</div>

  <!-- Modals for different states -->
  <div data-bhwf-modal="loading">
    <div class="bhwf-loader"></div>
    <span data-bhwf-progress></span>
    <span data-bhwf-progress-msg></span>
  </div>

  <div data-bhwf-modal="error">
    <h2>Something went wrong!</h2>
    <span data-bhwf-error-msg="api"></span>
    <button data-bhwf-button="tryAgain">Try again</button>
  </div>

  <div data-bhwf-modal="success">
    <h2>All done!</h2>
    <a data-bhwf-button="viewIssue" href="" target="_blank">View Your Issue</a>
  </div>
</form>
<head>
  <script src="https://unpkg.com/betahub-web-form@1.*.*/dist/bhwf.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/betahub-web-form@1.*.*/dist/bhwf.min.css" />
  <link rel="stylesheet" href="https://unpkg.com/betahub-web-form@1.*.*/dist/dropzone.min.css" />
</head>

<form
  data-bhwf-form
  data-bhwf-project-id="pr-123"
  data-bhwf-api-key="tkn-abc"
>
  <h1>Submit your issue</h1>

  <label>Description</label>
  <div class="bhwf-input-container">
    <textarea
      data-bhwf-input="description"
      data-bhwf-counter
      placeholder="Describe your issue as detailed as possible"
    ></textarea>
  </div>
  <span data-bhwf-error-msg="description"></span>

  <label>Steps to Reproduce</label>
  <textarea
    data-bhwf-input="stepsToReproduce"
    placeholder="Describe the steps leading to the issue"
  ></textarea>

  <label>Attach Media</label>
  <input type="file" data-bhwf-input="media" data-bhwf-dropzone multiple />

  <div data-bhwf-button="submit">Submit</div>

  <!-- Modals for different states -->
  <div data-bhwf-modal="loading">
    <div class="bhwf-loader"></div>
    <span data-bhwf-progress></span>
    <span data-bhwf-progress-msg></span>
  </div>

  <div data-bhwf-modal="error">
    <h2>Something went wrong!</h2>
    <span data-bhwf-error-msg="api"></span>
    <button data-bhwf-button="tryAgain">Try again</button>
  </div>

  <div data-bhwf-modal="success">
    <h2>All done!</h2>
    <a data-bhwf-button="viewIssue" href="" target="_blank">View Your Issue</a>
  </div>
</form>

For advanced use cases, the library offers a full programmatic API:

typescript
import BHWF from 'betahub-web-form';

const form = new BHWF.Form({
  projectId: 'pr-123',
  apiKey: 'tkn-abc',
  formElement: document.querySelector('form')
});

// Event handlers
form.on('loading', () => {
  console.log('Form is loading...');
});

form.on('success', (data) => {
  console.log('Issue created:', data.issueId);
  console.log('Issue URL:', data.issueUrl);
});

form.on('apiError', (data) => {
  console.error('API Error:', data.message);
});

form.on('progress', (data) => {
  console.log(`Progress: ${data.progress}% - ${data.message}`);
});

// Programmatic form control
form.submit();
import BHWF from 'betahub-web-form';

const form = new BHWF.Form({
  projectId: 'pr-123',
  apiKey: 'tkn-abc',
  formElement: document.querySelector('form')
});

// Event handlers
form.on('loading', () => {
  console.log('Form is loading...');
});

form.on('success', (data) => {
  console.log('Issue created:', data.issueId);
  console.log('Issue URL:', data.issueUrl);
});

form.on('apiError', (data) => {
  console.error('API Error:', data.message);
});

form.on('progress', (data) => {
  console.log(`Progress: ${data.progress}% - ${data.message}`);
});

// Programmatic form control
form.submit();

typescript
import BHWF from 'betahub-web-form';

const form = new BHWF.Form({
  projectId: 'pr-123',
  apiKey: 'tkn-abc',
  formElement: document.querySelector('form'),
  customElements: {
    description: {
      validator: (value: string): [boolean, string | undefined] => {
        if (!value) return [false, 'Description is required'];
        if (value.length < 50) return [false, 'Description must be at least 50 characters'];
        return [true, undefined];
      }
    },
    media: {
      validator: (files: FileList | undefined): [boolean, string | undefined] => {
        if (files && files.length > 5) return [false, 'Maximum 5 files allowed'];
        return [true, undefined];
      }
    }
  }
});
import BHWF from 'betahub-web-form';

const form = new BHWF.Form({
  projectId: 'pr-123',
  apiKey: 'tkn-abc',
  formElement: document.querySelector('form'),
  customElements: {
    description: {
      validator: (value: string): [boolean, string | undefined] => {
        if (!value) return [false, 'Description is required'];
        if (value.length < 50) return [false, 'Description must be at least 50 characters'];
        return [true, undefined];
      }
    },
    media: {
      validator: (files: FileList | undefined): [boolean, string | undefined] => {
        if (files && files.length > 5) return [false, 'Maximum 5 files allowed'];
        return [true, undefined];
      }
    }
  }
});

Problem: Integrating the dropzone.js library with native HTML <input type="file"> elements required solving the problem of data synchronization between two different file management systems.

Solution:

  • Implementation of transformIntoDropzone() function that hides the original input and creates a dropzone element
  • File synchronization through DataTransfer API
  • Automatic updating of input.files on every change in dropzone
typescript
const _syncFileInputFiles = () => {
  if (inputElement) {
    const fileList = new DataTransfer();
    dropzone.files.forEach((file) => fileList.items.add(file));
    inputElement.files = fileList.files;
    inputElement.dispatchEvent(new Event("input", { bubbles: true }));
  }
};
const _syncFileInputFiles = () => {
  if (inputElement) {
    const fileList = new DataTransfer();
    dropzone.files.forEach((file) => fileList.items.add(file));
    inputElement.files = fileList.files;
    inputElement.dispatchEvent(new Event("input", { bubbles: true }));
  }
};

Problem: Implementing precise progress tracking during upload of multiple files of different types (screenshots, videos, logs) to different API endpoints.

Solution:

  • Calculation of total size of all files before starting upload
  • Tracking progress of each file separately with global progress update
  • Handling different file types with automatic routing to appropriate endpoints
typescript
const totalFileSize: number = allFiles.reduce(
  (acc, file) => acc + (file.size || 0),
  0
);
let uploadedFileSize: number = 0;

// In uploadFile function:
const progress = (uploadedFileSize / totalFileSize) * 100;
this.emit("progress", {
  progress: progress > 100 ? 100 : progress,
  message: `Uploading files: ${uploadedFiles + 1}/${totalFiles}`,
});
const totalFileSize: number = allFiles.reduce(
  (acc, file) => acc + (file.size || 0),
  0
);
let uploadedFileSize: number = 0;

// In uploadFile function:
const progress = (uploadedFileSize / totalFileSize) * 100;
this.emit("progress", {
  progress: progress > 100 ? 100 : progress,
  message: `Uploading files: ${uploadedFiles + 1}/${totalFiles}`,
});

Problem: Implementing an intelligent system for recognizing file types in the media field and routing them to appropriate API endpoints.

Solution:

  • Analysis of file.type (MIME type) for automatic classification
  • Routing files to appropriate upload functions based on type
  • Fallback handling for unknown file types
typescript
for (const mediaFile of Array.from(fileInputsData.media)) {
  const fileType: string = mediaFile.type;
  if (fileType.startsWith("image/")) {
    await uploadFile(API.uploadScreenshot, { screenshot: mediaFile });
  } else if (fileType.startsWith("video/")) {
    await uploadFile(API.uploadVideoClip, { videoClip: mediaFile });
  } else {
    await uploadFile(API.uploadLogFile, { logFile: mediaFile });
  }
}
for (const mediaFile of Array.from(fileInputsData.media)) {
  const fileType: string = mediaFile.type;
  if (fileType.startsWith("image/")) {
    await uploadFile(API.uploadScreenshot, { screenshot: mediaFile });
  } else if (fileType.startsWith("video/")) {
    await uploadFile(API.uploadVideoClip, { videoClip: mediaFile });
  } else {
    await uploadFile(API.uploadLogFile, { logFile: mediaFile });
  }
}

Problem: Implementing a form state management system with handling of different states (loading, success, error) and automatic error clearing.

Solution:

  • Event-driven system with automatic setting of data-bhwf-state attribute
  • Automatic error clearing on every input change
  • Central state management through the Form class
typescript
this.on("loading", () => {
  this.formElement?.setAttribute("data-bhwf-state", "loading");
});

this.on("success", (data) => {
  this.formElement?.setAttribute("data-bhwf-state", "success");
  // Update issue links
});

this.formElement?.addEventListener("input", this.cleanErrors);
this.on("loading", () => {
  this.formElement?.setAttribute("data-bhwf-state", "loading");
});

this.on("success", (data) => {
  this.formElement?.setAttribute("data-bhwf-state", "success");
  // Update issue links
});

this.formElement?.addEventListener("input", this.cleanErrors);

Under normal circumstances, placing API keys on the frontend would be a security issue. In the case of BetaHub Web Form, this is a conscious decision justified by the application's specification:

  • Anonymous submissions: Issues can be submitted by anonymous users
  • Rate limiting: Server implements advanced request limiting
  • Server-side verification: All submissions require additional verification
  • Platform-side management: Keys can be deactivated at any time

The library implements basic client-side validation:

  • Data format: Checking correctness of format and length
  • File types: Validation of extensions and MIME types
  • File sizes: Checking limits before upload

BetaHub Web Form is a library that significantly facilitates creating external forms for reporting issues on the BetaHub platform. Its universality and ease of use make it an ideal solution for development teams needing quick integration of feedback collection systems.

Dual API Design: Creating two complementary integration methods - imperative for quick deployments and declarative for advanced use cases.

Modular architecture: System built from independent modules, ensuring easy testing, extension, and maintenance.

Performance optimization: Library size optimization through tree shaking and resource minification.

This project showed me how important it is to find a balance between ease of use and flexibility. Initially, I thought about creating only an imperative API with HTML attributes, but I quickly understood that programmers need greater control over form behavior.

The project also taught me that sometimes the best solutions are the simplest. Instead of creating a complicated configuration system, I went with a simple event-driven system with validation customization capabilities. This allowed maintaining simplicity for beginner users while giving advanced programmers full control.

Most satisfying was seeing how the library works in practice - from simple HTML forms to advanced React integrations. This confirmed that good API design can handle very different use cases without complicating basic functionality.

©2025 BatStack