0.1.5Updated 6 months ago
// deno-lint-ignore-file no-explicit-any

import { Reload } from "@islands/utils/reload.tsx";

type valid_value_types = string | boolean | number | Date | valid_value_types[]

interface PostifyError {
  data?: Record<string, any>
  message: string
  error: Error
}
type PostifyCallback<T extends Record<string, any> = Record<string, any>> = (error: PostifyError | null, response: T | null, extras: PostifyCallbackExtras) => void;

interface PostifyCallbackExtras {
  reload: () => void
  reset_form: () => void
}

interface PostifyOptions {
  callback?: PostifyCallback | null
  overrides?: Record<string, any>
}

function PostifyResetForm(this: HTMLFormElement) {
  this.reset();
}

export const Postify = ({ callback, overrides = {} }: PostifyOptions = {}) => async (e: SubmitEvent) => {
  e.preventDefault();
  e.stopPropagation();

  const form = e.target as HTMLFormElement;

  const data: Record<string, valid_value_types> = {};
  const input_elements = form.querySelectorAll<HTMLInputElement>('input[name]');

  for(const input of input_elements as any) {
    const name = input.name as string;
    let value: string | number | boolean | Date;

    switch(input.type) {
      case 'number': {
        value = parseFloat(input.value);
        break;
      }
      case 'checkbox': {
        value = input.checked;
        break;
      }
      case 'date': {
        value = new Date(input.value);
        break;
      }
      default: {
        value = input.value;
      }
    }

    if(data[name] === undefined) {
      data[name] = value;
    } else {
      data[name] = [data[name], value].flat();
    }
  }

  for(const [key, value] of Object.entries(overrides)) {
    data[key] = value;
  }

  const result = await fetch(form.action, {
    method: 'POST',
    body: JSON.stringify(data)
  });

  const extras: PostifyCallbackExtras = {
    reload: Reload,
    reset_form: PostifyResetForm.bind(form),
  }

  let json: Record<string, any> = {};
  const text = await result.text();

  try {
    json = JSON.parse(text);
  } catch(_) {/* */}

  if(!result.ok) return callback?.({message: json?.message || json?.reason || text || 'Unspecified error', error: new Error(`${result.statusText}\n${text}`)}, null, extras);

  try {
    callback?.(null, json, extras);
  } catch(e: any) {
    callback?.({message: e.message, error: new Error(`Could not parse response json. [${e.message}]`)}, null, extras);
  }

  return false;
}