patx/youtube-clone

/** Sign in and create account, on one switchable card. */
import { h } from '../dom.js';
import { api } from '../api.js';
import { setUser, refreshSubscriptions } from '../store.js';
import { navigate, currentQuery } from '../router.js';
import { toast } from '../ui.js';

export function authView(mode) {
  return async () => {
    const next = currentQuery().get('next') || '/';
    const isSignup = mode === 'signup';
    document.title = isSignup ? 'Create account · MeTube' : 'Sign in · MeTube';

    const error = h('div.formerror', { hidden: true, role: 'alert' });
    const submit = h('button.btn.btn--primary.btn--full', { type: 'submit' },
      isSignup ? 'Create account' : 'Sign in');

    const fields = isSignup
      ? {
          username: h('input.input', { name: 'username', autocomplete: 'username', required: true,
            placeholder: 'yourhandle', maxlength: '24', pattern: '[a-zA-Z0-9_.]{3,24}' }),
          displayName: h('input.input', { name: 'displayName', autocomplete: 'name',
            placeholder: 'How your name appears', maxlength: '40' }),
          email: h('input.input', { name: 'email', type: 'email', autocomplete: 'email', required: true,
            placeholder: '[email protected]' }),
          password: h('input.input', { name: 'password', type: 'password', autocomplete: 'new-password',
            required: true, minlength: '8', placeholder: 'At least 8 characters' }),
        }
      : {
          identifier: h('input.input', { name: 'identifier', autocomplete: 'username', required: true,
            placeholder: 'Username or email' }),
          password: h('input.input', { name: 'password', type: 'password', autocomplete: 'current-password',
            required: true, placeholder: 'Your password' }),
        };

    const form = h('form.panel', {
      novalidate: true,
      onsubmit: async (event) => {
        event.preventDefault();
        error.hidden = true;
        submit.disabled = true;
        submit.textContent = isSignup ? 'Creating account…' : 'Signing in…';
        try {
          const payload = Object.fromEntries(
            Object.entries(fields).map(([key, input]) => [key, input.value.trim()]),
          );
          const { user } = isSignup ? await api.signup(payload) : await api.login(payload);
          setUser(user);
          await refreshSubscriptions();
          toast(isSignup ? `Welcome to MeTube, ${user.displayName}` : `Signed in as ${user.displayName}`, 'good');
          navigate(next.startsWith('/') ? next : '/', { replace: true });
        } catch (err) {
          error.textContent = err.message;
          error.hidden = false;
          submit.disabled = false;
          submit.textContent = isSignup ? 'Create account' : 'Sign in';
        }
      },
    },
      error,
      ...(isSignup
        ? [
            field('Username', fields.username, 'Letters, numbers, dot and underscore. This becomes your @handle.'),
            field('Display name', fields.displayName, 'Optional — defaults to your username.'),
            field('Email', fields.email),
            field('Password', fields.password, 'At least 8 characters.'),
          ]
        : [
            field('Username or email', fields.identifier),
            field('Password', fields.password),
          ]),
      submit,
    );

    return h('div.auth', {},
      h('div.auth__card', {},
        h('div.auth__head', {},
          h('h1.auth__title', { text: isSignup ? 'Create your channel' : 'Welcome back' }),
          h('div.auth__sub', {
            text: isSignup
              ? 'One account to upload, comment and subscribe.'
              : 'Sign in to pick up where you left off.',
          }),
        ),
        form,
        h('div.auth__switch', {},
          isSignup ? 'Already have an account? ' : 'New to MeTube? ',
          h('button', {
            type: 'button',
            onclick: () => navigate(`/${isSignup ? 'signin' : 'signup'}?next=${encodeURIComponent(next)}`),
          }, isSignup ? 'Sign in' : 'Create an account'),
        ),
        isSignup ? null : h('div.auth__demo', {},
          h('b', {}, 'Trying the demo?'), ' Sign in as ',
          h('code', {}, 'pixelforge'), ', ', h('code', {}, 'quietkitchen'), ' or ', h('code', {}, 'marta'),
          ' with the password ', h('code', {}, 'password123'), '.',
        ),
      ),
    );
  };
}

const field = (label, input, hint) =>
  h('div.field', {},
    h('label.field__label', { text: label }),
    input,
    hint ? h('div.field__hint', { text: hint }) : null,
  );