preact#FunctionalComponent TypeScript Examples

The following examples show how to use preact#FunctionalComponent. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
Header: FunctionalComponent<Props> = ({ user }) => {
  if (!user) return <header />;

  return (
    <header class="main-header">
      <img
        width="40"
        height="40"
        alt={user.name}
        src={`${user.picture}=s${40}-c`}
        srcset={`${user.picture}=s${80}-c 2x`}
        tabIndex={0}
        class="user-image"
      />
      <form method="post" action="/auth/logout">
        <button class="unbutton log-out-button">Logout</button>
      </form>
    </header>
  );
}
Example #2
Source File: index.tsx    From netless-app with MIT License 6 votes vote down vote up
TimeCell: FunctionalComponent<TimeCellProps> = memo(
  ({ digit, disabled, onUp, onDown }) => {
    const [oldDigit, setOldDigit] = useState(0);
    const [flipped, setFlipped] = useState(false);

    useEffect(() => {
      if (disabled) {
        setFlipped(true);
        const timeout = window.setTimeout(() => {
          setFlipped(false);
          setOldDigit(digit);
        }, 500);
        return () => window.clearTimeout(timeout);
      } else {
        setOldDigit(digit);
      }
    }, [digit]);

    return (
      <div class={classNames("time-cell", { disabled })}>
        <div class="time-cell-up" onClick={disabled ? void 0 : onUp} />
        <div class="rotor">
          <div class={classNames("rotor-leaf", { flipped })}>
            <figure class="rotor-leaf-rear">{digit}</figure>
            <figure class="rotor-leaf-front">{oldDigit}</figure>
          </div>
          <div class="rotor-top">{digit}</div>
          <div class="rotor-bottom">{oldDigit}</div>
        </div>
        <div class="time-cell-down" onClick={disabled ? void 0 : onDown} />
      </div>
    );
  }
)
Example #3
Source File: index.tsx    From tic-tac-toe-app with MIT License 6 votes vote down vote up
Notfound: FunctionalComponent = () => {
    return (
        <div class={style.notfound}>
            <h1>Error 404</h1>
            <p>That page doesn't exist.</p>
            <Link href="/"><h4>Back to Home</h4></Link>
        </div>
    );
}
Example #4
Source File: index.tsx    From tic-tac-toe-app with MIT License 6 votes vote down vote up
Privacy: FunctionalComponent = () => {
    return (
        <Container>
            <div class={style.privacy}>
                We only collect anonymous user data (IP address, software and
                hardware info) when the app crashes to make the user experience
                better.
            </div>
        </Container>
    );
}
Example #5
Source File: index.tsx    From tic-tac-toe-app with MIT License 6 votes vote down vote up
Header: FunctionalComponent<PropTypes> = ({ title }) => {
    return (
        <header class={style.header}>
            <h1>{title}</h1>
            <nav>
                <Link activeClassName={style.active} href="/">
                    Home
                </Link>
                <Link activeClassName={style.active} href="/privacy">
                    Privacy
                </Link>
            </nav>
        </header>
    );
}
Example #6
Source File: app.tsx    From tic-tac-toe-app with MIT License 6 votes vote down vote up
App: FunctionalComponent = () => {
    return (
        <div id="app">
            <Header title="Tic Tac Toe" />
            <Router>
                <Route path="/" component={Home} />
                <Route path="/privacy/" component={Privacy} />
                <NotFoundPage default />
            </Router>
        </div>
    );
}
Example #7
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
LoggedOut: FunctionalComponent = () => {
  return (
    <UserPage>
      <div class="log-in-container">
        <form method="post" action="/auth/login">
          <button
            class="unbutton vote-button"
            style={{
              '--color-from': getHexColor(palette[5][0]),
              '--color-to': getHexColor(palette[5][1]),
            }}
          >
            Log in
          </button>
        </form>
      </div>
    </UserPage>
  );
}
Example #8
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
LoggedIn: FunctionalComponent<Props> = ({ user, initialState }) => {
  return (
    <UserPage user={user}>
      <script type="module" src={bundleURL} />
      {imports.map(i => (
        // @ts-ignore https://github.com/preactjs/preact/pull/2068
        <link rel="preload" as="script" href={i} crossOrigin="" />
      ))}
      <script
        dangerouslySetInnerHTML={{
          __html: `self.initialState = ${JSON.stringify(initialState)}`,
        }}
      ></script>
      <div class="vote-container" />
    </UserPage>
  );
}
Example #9
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
BigScreenIframePage: FunctionalComponent<{}> = () => {
  return (
    <html>
      <head>
        <title>{title}</title>
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <link
          rel="icon"
          href="https://cdn.glitch.com/b7996c5b-5a36-4f1b-84db-52a31d101dfc%2Ffavicon.png?v=1577974253219"
        />
        <link rel="stylesheet" href={cssPath} />
        <script type="module" src={bundleURL} />
        {imports.map(i => (
          // @ts-ignore https://github.com/preactjs/preact/pull/2068
          <link rel="preload" as="script" href={i} crossOrigin="" />
        ))}
      </head>
      <body>
        <div class="big-screen-container"></div>
      </body>
    </html>
  );
}
Example #10
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
BigScreenPage: FunctionalComponent<{}> = () => {
  return (
    <html>
      <head>
        <title>{title}</title>
        <meta
          name="viewport"
          content="width=device-width,initial-scale=1,user-scalable=no"
        />
        <link
          rel="icon"
          href="https://cdn.glitch.com/b7996c5b-5a36-4f1b-84db-52a31d101dfc%2Ffavicon.png?v=1577974253219"
        />
        <link rel="stylesheet" href={cssPath} />
        <script type="module" src={bundleURL} />
        {imports.map(i => (
          // @ts-ignore https://github.com/preactjs/preact/pull/2068
          <link rel="preload" as="script" href={i} crossOrigin="" />
        ))}
      </head>
      <body>
        <iframe src="/big-screen/iframe/" />
      </body>
    </html>
  );
}
Example #11
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
TopicsAdminPage: FunctionalComponent = () => {
  return (
    <html>
      <head>
        <title>Topics - Admin - {title}</title>
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <link rel="stylesheet" href={cssPath} />
        <script type="module" src={bundleURL} />
      </head>
      <body>
        <h1>Topics</h1>
        <p>
          <a href="/admin/">Back</a>
        </p>
        <div class="topics-container" />
      </body>
    </html>
  );
}
Example #12
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
AdminSimpleLogin: FunctionalComponent = () => {
  return (
    <html>
      <head>
        <title>Admin - {title}</title>
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <link rel="stylesheet" href={cssPath} />
      </head>
      <body>
        <h1>Login</h1>
        <form action="/auth/admin-login" method="POST" class="admin-form-items">
          <div class="admin-form-item">
            <div>
              <label>
                <span class="label">Password</span>
                <input
                  class="input"
                  type="password"
                  name="password"
                  autoFocus
                />
              </label>
            </div>
            <div>
              <button class="button">Log in</button>
            </div>
          </div>
        </form>
      </body>
    </html>
  );
}
Example #13
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
AdminPage: FunctionalComponent = () => {
  return (
    <html>
      <head>
        <title>Admin - {title}</title>
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <link
          rel="icon"
          href="https://cdn.glitch.com/b7996c5b-5a36-4f1b-84db-52a31d101dfc%2Ffavicon.png?v=1577974253219"
        />
        <link rel="stylesheet" href={cssPath} />
        <script type="module" src={bundleURL} />
        {imports.map(i => (
          // @ts-ignore https://github.com/preactjs/preact/pull/2068
          <link rel="preload" as="script" href={i} crossOrigin="" />
        ))}
      </head>
      <body>
        <h1>Admin</h1>
        <p>
          <a href="/admin/topics/">Edit topics</a>
        </p>
        <div class="admin-container" />
      </body>
    </html>
  );
}
Example #14
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
Select: FunctionalComponent<JSX.HTMLAttributes> = props => {
  const { class: className, ...selectProps } = props;

  return (
    <div class={'select' + (className ? ' ' + className : '')}>
      <select {...selectProps}></select>
      <div class="select-icon">
        <svg class="fill-current h-4 w-4" viewBox="0 0 20 20">
          <path d="M9.3 13l.7.7L15.7 8l-1.5-1.4-4.2 4.2-4.2-4.2L4.3 8z" />
        </svg>
      </div>
    </div>
  );
}
Example #15
Source File: vs.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
VS: FunctionalComponent<Props> = ({
  colorFrom,
  colorTo,
  children,
}: RenderableProps<Props>) => (
  <div
    class="vs-circle"
    style={{
      '--color-from': colorFrom || '',
      '--color-to': colorTo || '',
    }}
  >
    <div class="vs-outer vs-outer-1"></div>
    <div class="vs-outer vs-outer-2"></div>
    <div class="vs-outer vs-outer-3"></div>
    <div class="vs-outer vs-outer-4"></div>
    <div class="vs-innermost-circle">{children || 'VS'}</div>
  </div>
)
Example #16
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
AdminVoteWrapper: FunctionalComponent<WrapperProps> = ({
  children,
  onNewVoteClick,
}) => (
  <section>
    <h1>Active vote</h1>
    {children}
    <p>
      <button class="button" onClick={onNewVoteClick}>
        New vote
      </button>
    </p>
  </section>
)
Example #17
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
TopicsSelect: FunctionalComponent<{
  value: string;
  topics: Topics;
  onInput: (event: Event) => void;
}> = ({ onInput, topics, value }) => {
  const sortedTopics = Object.entries(topics).sort((a, b) =>
    a[1].label > b[1].label ? 1 : -1,
  );

  return (
    <Select onInput={onInput} value={value}>
      <option value=""></option>
      {sortedTopics.map(([id, topic]) => (
        <option value={id}>{topic.label}</option>
      ))}
    </Select>
  );
}
Example #18
Source File: index.tsx    From big-web-quiz with Apache License 2.0 6 votes vote down vote up
AdminBracketWrapper: FunctionalComponent<innerProps> = ({
  children,
  onGenerateClick,
  onZoomOut,
}) => (
  <section>
    <h1>Bracket</h1>
    {children}
    <div class="reset-row">
      <button class="button button-danger" onClick={onGenerateClick}>
        Regenerate bracket
      </button>{' '}
      <button class="button" onClick={onZoomOut}>
        Zoom out
      </button>
    </div>
  </section>
)
Example #19
Source File: index.tsx    From big-web-quiz with Apache License 2.0 5 votes vote down vote up
UserPage: FunctionalComponent<Props> = ({ children, user }) => {
  return (
    <html>
      <head>
        <title>{title}</title>
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <link
          rel="preload"
          as="font"
          crossOrigin=""
          href="https://cdn.glitch.com/b7996c5b-5a36-4f1b-84db-52a31d101dfc%2Fnormal.woff2?v=1577974248786"
        />
        <link
          rel="preload"
          as="font"
          crossOrigin=""
          href="https://cdn.glitch.com/b7996c5b-5a36-4f1b-84db-52a31d101dfc%2Fbold.woff2?v=1577974248594"
        />
        <link
          rel="icon"
          href="https://cdn.glitch.com/b7996c5b-5a36-4f1b-84db-52a31d101dfc%2Ffavicon.png?v=1577974253219"
        />
        <style
          dangerouslySetInnerHTML={{
            __html: inlineCSS,
          }}
        ></style>
      </head>
      <body>
        <div class="main-ui">
          <Header user={user} />
          <div class="main-content-container">
            <div class="main-content">
              <div class="site-title">
                <h1>{title}</h1>
                <p>{subTitle}</p>
              </div>
              <div class="main-action-area">{children}</div>
            </div>
          </div>
        </div>
      </body>
    </html>
  );
}
Example #20
Source File: index.tsx    From tic-tac-toe-app with MIT License 5 votes vote down vote up
Container: FunctionalComponent = ({ children }) => {
    return <div class={style.container}>{children}</div>;
}
Example #21
Source File: index.tsx    From big-web-quiz with Apache License 2.0 5 votes vote down vote up
AdminSelectedBracketWrapper: FunctionalComponent<{}> = ({ children }) => (
  <section>
    <h1>Selected bracket</h1>
    {children}
  </section>
)
Example #22
Source File: index.tsx    From netless-app with MIT License 5 votes vote down vote up
Clock: FunctionalComponent<ClockProps> = memo(
  ({ minutes, seconds, disabled, onAdjustTime }) => {
    const mins = useDigits(minutes);
    const secs = useDigits(seconds);

    const addTenMinutes = useAdjustTime(onAdjustTime, TimeAdjustment.AddTenMinutes);
    const reduceTenMinutes = useAdjustTime(onAdjustTime, TimeAdjustment.ReduceTenMinutes);
    const addOneMinute = useAdjustTime(onAdjustTime, TimeAdjustment.AddOneMinute);
    const reduceOneMinute = useAdjustTime(onAdjustTime, TimeAdjustment.ReduceOneMinute);
    const addTenSeconds = useAdjustTime(onAdjustTime, TimeAdjustment.AddTenSeconds);
    const reduceTenSeconds = useAdjustTime(onAdjustTime, TimeAdjustment.ReduceTenSeconds);
    const addOneSecond = useAdjustTime(onAdjustTime, TimeAdjustment.AddOneSecond);
    const reduceOneSecond = useAdjustTime(onAdjustTime, TimeAdjustment.ReduceOneSecond);

    return (
      <div className="countdown-clock">
        <TimeCell
          disabled={disabled}
          digit={mins[0]}
          onUp={addTenMinutes}
          onDown={reduceTenMinutes}
        />
        <TimeCell
          digit={mins[1]}
          disabled={disabled}
          onUp={addOneMinute}
          onDown={reduceOneMinute}
        />
        <div class="countdown-clock-divider" />
        <TimeCell
          disabled={disabled}
          digit={secs[0]}
          onUp={addTenSeconds}
          onDown={reduceTenSeconds}
        />
        <TimeCell
          digit={secs[1]}
          disabled={disabled}
          onUp={addOneSecond}
          onDown={reduceOneSecond}
        />
      </div>
    );
  }
)
Example #23
Source File: index.tsx    From netless-app with MIT License 5 votes vote down vote up
Countdown: FunctionalComponent<CountdownProps> = memo(
  ({
    readonly,
    countdownSecs,
    startTime,
    paused,
    onAdjustTime,
    onStart,
    onPause,
    onResume,
    onReset,
  }) => {
    const [now, setNow] = useState(0);

    const started = startTime > 0 && now >= startTime;
    const amountInSecs = countdownSecs - (started ? now - startTime : 0);
    const minutes = Math.floor(amountInSecs / 60);
    const seconds = amountInSecs - minutes * 60;

    useEffect(() => {
      let timeout = NaN;
      if (countdownSecs > 0 && startTime > 0 && !paused) {
        const updateNow = () => {
          setNow(Math.floor(Date.now() / 1000));
          timeout = window.requestAnimationFrame(updateNow);
        };
        updateNow();
        return () => window.cancelAnimationFrame(timeout);
      } else {
        window.cancelAnimationFrame(timeout);
      }
    }, [startTime, paused, countdownSecs]);

    useEffect(() => {
      if (startTime > 0 && now - startTime >= countdownSecs) {
        onReset();
      }
    }, [now, startTime, countdownSecs, onReset]);

    return (
      <div class="netless-app-countdown">
        <div class="netless-app-countdown-shrink">
          <Clock
            minutes={minutes}
            seconds={seconds}
            disabled={readonly || started}
            onAdjustTime={onAdjustTime}
          />
          <div class="netless-app-countdown-btns">
            {paused ? (
              <>
                <button onClick={onReset} disabled={readonly}>
                  Reset
                </button>
                <button onClick={onResume} disabled={readonly}>
                  Resume
                </button>
              </>
            ) : started ? (
              <button onClick={onPause} disabled={readonly}>
                Pause
              </button>
            ) : (
              <button onClick={onStart} disabled={readonly || amountInSecs <= 0}>
                Start
              </button>
            )}
          </div>
        </div>
      </div>
    );
  }
)
Example #24
Source File: CardInput.tsx    From adyen-web with MIT License 4 votes vote down vote up
CardInput: FunctionalComponent<CardInputProps> = props => {
    const sfp = useRef(null);
    const billingAddressRef = useRef(null);
    const isValidating = useRef(false);

    const cardInputRef = useRef<CardInputRef>({});
    // Just call once
    if (!Object.keys(cardInputRef.current).length) {
        props.setComponentRef(cardInputRef.current);
    }

    const hasPanLengthRef = useRef(0);
    const isAutoJumping = useRef(false);

    const errorFieldId = 'creditCardErrors';

    const { collateErrors, moveFocus, showPanel } = props.SRConfig;

    const specifications = useMemo(() => new Specifications(props.specifications), [props.specifications]);

    // Creates access to sfp so we can call functionality on it (like handleOnAutoComplete) directly from the console. Used for testing.
    if (process.env.NODE_ENV === 'development') cardInputRef.current.sfp = sfp;

    /**
     * STATE HOOKS
     */
    const [status, setStatus] = useState('ready');

    const [errors, setErrors] = useState<CardInputErrorState>({});
    const [valid, setValid] = useState<CardInputValidState>({
        ...(props.holderNameRequired && { holderName: false })
    });
    const [data, setData] = useState<CardInputDataState>({
        ...(props.hasHolderName && { holderName: props.data.holderName ?? '' })
    });

    // An object containing a collection of all the errors that can be passed to the ErrorPanel to be read by the screenreader
    const [mergedSRErrors, setMergedSRErrors] = useState<ErrorPanelObj>(null);

    const [focusedElement, setFocusedElement] = useState('');
    const [isSfpValid, setIsSfpValid] = useState(false);
    const [expiryDatePolicy, setExpiryDatePolicy] = useState(DATE_POLICY_REQUIRED);
    const [cvcPolicy, setCvcPolicy] = useState(CVC_POLICY_REQUIRED);
    const [issuingCountryCode, setIssuingCountryCode] = useState<string>(null);

    const [dualBrandSelectElements, setDualBrandSelectElements] = useState([]);
    const [selectedBrandValue, setSelectedBrandValue] = useState('');

    const showBillingAddress = props.billingAddressMode !== AddressModeOptions.none && props.billingAddressRequired;

    const partialAddressSchema = handlePartialAddressMode(props.billingAddressMode);

    const [storePaymentMethod, setStorePaymentMethod] = useState(false);
    const [billingAddress, setBillingAddress] = useState<AddressData>(showBillingAddress ? props.data.billingAddress : null);
    const [showSocialSecurityNumber, setShowSocialSecurityNumber] = useState(false);
    const [socialSecurityNumber, setSocialSecurityNumber] = useState('');
    const [installments, setInstallments] = useState<InstallmentsObj>({ value: null });

    // re. Disable arrows for iOS: The name of the element calling for other elements to be disabled
    // - either a securedField type (like 'encryptedCardNumber') when call is coming from SF
    // or else the name of an internal, Adyen-web, element (like 'holderName')
    const [iOSFocusedField, setIOSFocusedField] = useState(null);

    /**
     * LOCAL VARS
     */
    const {
        handleChangeFor,
        triggerValidation,
        data: formData,
        valid: formValid,
        errors: formErrors,
        setSchema,
        setData: setFormData,
        setValid: setFormValid,
        setErrors: setFormErrors
    } = useForm<CardInputDataState>({
        schema: [],
        defaultData: props.data,
        formatters: cardInputFormatters,
        rules: cardInputValidationRules
    });

    const hasInstallments = !!Object.keys(props.installmentOptions).length;
    const showAmountsInInstallments = props.showInstallmentAmounts ?? true;

    const cardCountryCode: string = issuingCountryCode ?? props.countryCode;
    const isKorea = cardCountryCode === 'kr'; // If issuingCountryCode or the merchant defined countryCode is set to 'kr'
    const showKCP = props.configuration.koreanAuthenticationRequired && isKorea;

    const showBrazilianSSN: boolean =
        (showSocialSecurityNumber && props.configuration.socialSecurityNumberMode === 'auto') ||
        props.configuration.socialSecurityNumberMode === 'show';

    /**
     * HANDLERS
     */
    // SecuredField-only handler
    const handleFocus = getFocusHandler(setFocusedElement, props.onFocus, props.onBlur);

    const retrieveLayout = () => {
        return getLayout({
            props,
            showKCP,
            showBrazilianSSN,
            ...(props.billingAddressRequired && {
                countrySpecificSchemas: specifications.getAddressSchemaForCountry(billingAddress?.country),
                billingAddressRequiredFields: props.billingAddressRequiredFields
            })
        });
    };

    /**
     * re. Disabling arrow keys in iOS:
     * Only by disabling all fields in the Card PM except for the active securedField input can we force the iOS soft keyboard arrow keys to disable
     *
     * @param obj - has fieldType prop saying whether this function is being called in response to an securedFields click ('encryptedCardNumber' etc)
     * - in which case we should disable all non-SF fields
     * or,
     * due to an internal action ('webInternalElement') - in which case we can enable all non-SF fields
     */
    const handleTouchstartIOS = useCallback((obj: TouchStartEventObj) => {
        const elementType = obj.fieldType !== 'webInternalElement' ? obj.fieldType : obj.name;
        setIOSFocusedField(elementType);
    }, []);

    // Callback for ErrorPanel
    const handleErrorPanelFocus = getErrorPanelHandler(isValidating, sfp, handleFocus);

    const handleAddress = getAddressHandler(setFormData, setFormValid, setFormErrors);

    const doPanAutoJump = getAutoJumpHandler(isAutoJumping, sfp, retrieveLayout());

    const handleSecuredFieldsChange = (sfState: SFPState, eventDetails?: OnChangeEventDetails): void => {
        // Clear errors so that the screenreader will read them *all* again - without this it only reads the newly added ones
        setMergedSRErrors(null);

        /**
         * Handling auto complete value for holderName (but only if the component is using a holderName field)
         */
        if (sfState.autoCompleteName) {
            if (!props.hasHolderName) return;
            const holderNameValidationFn = getRuleByNameAndMode('holderName', 'blur');
            const acHolderName = holderNameValidationFn(sfState.autoCompleteName) ? sfState.autoCompleteName : null;
            if (acHolderName) {
                setFormData('holderName', acHolderName);
                setFormValid('holderName', true); // only if holderName is valid does this fny get called - so we know it's valid and w/o error
                setFormErrors('holderName', null);
            }
            return;
        }

        /**
         * If PAN has just become valid: decide if we can shift focus to the next field.
         *
         * We can if the config prop, autoFocus, is true AND we have a panLength value from binLookup AND one of the following scenarios is true:
         *  - If encryptedCardNumber was invalid but now is valid
         *      [scenario: shopper has typed in a number and field is now valid]
         *  - If encryptedCardNumber was valid and still is valid and we're handling an onBrand event (triggered by binLookup which has happened after the handleOnFieldValid event)
         *     [scenario: shopper has pasted in a full, valid, number]
         */
        if (
            props.autoFocus &&
            hasPanLengthRef.current > 0 &&
            ((!valid.encryptedCardNumber && sfState.valid?.encryptedCardNumber) ||
                (valid.encryptedCardNumber && sfState.valid.encryptedCardNumber && eventDetails.event === 'handleOnBrand'))
        ) {
            doPanAutoJump();
        }

        /**
         * Process SFP state
         */
        setData({ ...data, ...sfState.data });
        setErrors({ ...errors, ...sfState.errors });
        setValid({ ...valid, ...sfState.valid });

        setIsSfpValid(sfState.isSfpValid);

        // Values relating to /binLookup response
        setCvcPolicy(sfState.cvcPolicy);
        setShowSocialSecurityNumber(sfState.showSocialSecurityNumber);
        setExpiryDatePolicy(sfState.expiryDatePolicy);
    };

    // Farm the handlers for binLookup related functionality out to another 'extensions' file
    const extensions = useMemo(
        () =>
            CIExtensions(
                props,
                { sfp },
                { dualBrandSelectElements, setDualBrandSelectElements, setSelectedBrandValue, issuingCountryCode, setIssuingCountryCode },
                hasPanLengthRef
            ),
        [dualBrandSelectElements, issuingCountryCode]
    );

    /**
     * EXPOSE METHODS expected by Card.tsx
     */
    cardInputRef.current.showValidation = () => {
        // Clear errors so that the screenreader will read them *all* again
        setMergedSRErrors(null);

        // Validate SecuredFields
        sfp.current.showValidation();

        // Validate holderName & SSN & KCP (taxNumber) but *not* billingAddress
        triggerValidation(['holderName', 'socialSecurityNumber', 'taxNumber']);

        // Validate Address
        if (billingAddressRef?.current) billingAddressRef.current.showValidation();

        isValidating.current = true;
    };

    cardInputRef.current.processBinLookupResponse = (binLookupResponse: BinLookupResponse, isReset: boolean) => {
        extensions.processBinLookup(binLookupResponse, isReset);
    };

    cardInputRef.current.setStatus = setStatus;

    /**
     * EFFECT HOOKS
     */
    useEffect(() => {
        // componentDidMount - expose more methods expected by Card.tsx
        cardInputRef.current.setFocusOn = sfp.current.setFocusOn;
        cardInputRef.current.updateStyles = sfp.current.updateStyles;
        cardInputRef.current.handleUnsupportedCard = sfp.current.handleUnsupportedCard;

        // componentWillUnmount
        return () => {
            sfp.current.destroy();
        };
    }, []);

    /**
     * Handle form schema updates
     */
    useEffect(() => {
        const newSchema = [
            ...(props.hasHolderName ? ['holderName'] : []),
            ...(showBrazilianSSN ? ['socialSecurityNumber'] : []),
            ...(showKCP ? ['taxNumber'] : []),
            ...(showBillingAddress ? ['billingAddress'] : [])
        ];
        setSchema(newSchema);
    }, [props.hasHolderName, showBrazilianSSN, showKCP]);

    /**
     * Handle updates from useForm
     */
    useEffect(() => {
        // Clear errors so that the screenreader will read them *all* again
        setMergedSRErrors(null);

        setData({ ...data, holderName: formData.holderName ?? '', taxNumber: formData.taxNumber });

        setSocialSecurityNumber(formData.socialSecurityNumber);

        if (showBillingAddress) setBillingAddress({ ...formData.billingAddress });

        setValid({
            ...valid,
            holderName: props.holderNameRequired ? formValid.holderName : true,
            // Setting value to false if it's falsy keeps in line with existing, expected behaviour
            // - but there is an argument to allow 'undefined' as a value to indicate the non-presence of the field
            socialSecurityNumber: formValid.socialSecurityNumber ? formValid.socialSecurityNumber : false,
            taxNumber: formValid.taxNumber ? formValid.taxNumber : false,
            billingAddress: formValid.billingAddress ? formValid.billingAddress : false
        });

        // Check if billingAddress errors object has any properties that aren't null or undefined
        const addressHasErrors = formErrors.billingAddress
            ? Object.entries(formErrors.billingAddress).reduce((acc, [, error]) => acc || error != null, false)
            : false;

        // Errors
        setErrors({
            ...errors,
            holderName: props.holderNameRequired && !!formErrors.holderName ? formErrors.holderName : null,
            socialSecurityNumber: showBrazilianSSN && !!formErrors.socialSecurityNumber ? formErrors.socialSecurityNumber : null,
            taxNumber: showKCP && !!formErrors.taxNumber ? formErrors.taxNumber : null,
            billingAddress: showBillingAddress && addressHasErrors ? formErrors.billingAddress : null
        });
    }, [formData, formValid, formErrors]);

    /**
     * Main 'componentDidUpdate' handler
     */
    useEffect(() => {
        const holderNameValid: boolean = valid.holderName;

        const sfpValid: boolean = isSfpValid;
        const addressValid: boolean = showBillingAddress ? valid.billingAddress : true;

        const koreanAuthentication: boolean = showKCP ? !!valid.taxNumber && !!valid.encryptedPassword : true;

        const socialSecurityNumberValid: boolean = showBrazilianSSN ? !!valid.socialSecurityNumber : true;

        const isValid: boolean = sfpValid && holderNameValid && addressValid && koreanAuthentication && socialSecurityNumberValid;

        const sfStateErrorsObj = sfp.current.mapErrorsToValidationRuleResult();
        const mergedErrors = { ...errors, ...sfStateErrorsObj }; // maps sfErrors AND solves race condition problems for sfp from showValidation

        // Extract and then flatten billingAddress errors into a new object with *all* the field errors at top level
        const { billingAddress: extractedAddressErrors, ...errorsWithoutAddress } = mergedErrors;
        const errorsForPanel = { ...errorsWithoutAddress, ...extractedAddressErrors };

        const sortedMergedErrors = sortErrorsForPanel({
            errors: errorsForPanel,
            layout: retrieveLayout(),
            i18n: props.i18n,
            countrySpecificLabels: specifications.getAddressLabelsForCountry(billingAddress?.country)
        });
        setMergedSRErrors(sortedMergedErrors);

        props.onChange({
            data,
            valid,
            errors: mergedErrors,
            isValid,
            billingAddress,
            selectedBrandValue,
            storePaymentMethod,
            socialSecurityNumber,
            installments
        });
    }, [data, valid, errors, selectedBrandValue, storePaymentMethod, installments]);

    /**
     * RENDER
     */
    const FieldToRender = props.storedPaymentMethodId ? StoredCardFieldsWrapper : CardFieldsWrapper;

    return (
        <Fragment>
            <SecuredFieldsProvider
                ref={sfp}
                {...extractPropsForSFP(props)}
                styles={{ ...defaultStyles, ...props.styles }}
                koreanAuthenticationRequired={props.configuration.koreanAuthenticationRequired}
                hasKoreanFields={!!(props.configuration.koreanAuthenticationRequired && props.countryCode === 'kr')}
                onChange={handleSecuredFieldsChange}
                onBrand={props.onBrand}
                onFocus={handleFocus}
                type={props.brand}
                isCollatingErrors={collateErrors}
                onTouchstartIOS={handleTouchstartIOS}
                render={({ setRootNode, setFocusOn }, sfpState) => (
                    <div
                        ref={setRootNode}
                        className={`adyen-checkout__card-input ${styles['card-input__wrapper']} adyen-checkout__card-input--${props.fundingSource ??
                            'credit'}`}
                        role={collateErrors && 'form'}
                        aria-describedby={collateErrors ? errorFieldId : null}
                    >
                        <FieldToRender
                            // Extract exact props that we need to pass down
                            {...extractPropsForCardFields(props)}
                            // Pass on vars created in CardInput:
                            // Base (shared w. StoredCard)
                            data={data}
                            valid={valid}
                            errors={errors}
                            handleChangeFor={handleChangeFor}
                            focusedElement={focusedElement}
                            setFocusOn={setFocusOn}
                            sfpState={sfpState}
                            collateErrors={collateErrors}
                            errorFieldId={errorFieldId}
                            cvcPolicy={cvcPolicy}
                            hasInstallments={hasInstallments}
                            showAmountsInInstallments={showAmountsInInstallments}
                            handleInstallments={setInstallments}
                            // For Card
                            brandsIcons={props.brandsIcons}
                            mergedSRErrors={mergedSRErrors}
                            moveFocus={moveFocus}
                            showPanel={showPanel}
                            handleErrorPanelFocus={handleErrorPanelFocus}
                            formData={formData}
                            formErrors={formErrors}
                            formValid={formValid}
                            expiryDatePolicy={expiryDatePolicy}
                            dualBrandSelectElements={dualBrandSelectElements}
                            extensions={extensions}
                            selectedBrandValue={selectedBrandValue}
                            // For KCP
                            showKCP={showKCP}
                            // For SSN
                            showBrazilianSSN={showBrazilianSSN}
                            socialSecurityNumber={socialSecurityNumber}
                            // For Store details
                            handleOnStoreDetails={setStorePaymentMethod}
                            // For Address
                            billingAddress={billingAddress}
                            handleAddress={handleAddress}
                            billingAddressRef={billingAddressRef}
                            partialAddressSchema={partialAddressSchema}
                            //
                            iOSFocusedField={iOSFocusedField}
                        />
                    </div>
                )}
            />
            {props.showPayButton &&
                props.payButton({
                    status,
                    icon: getImage({ loadingContext: props.loadingContext, imageFolder: 'components/' })('lock')
                })}
        </Fragment>
    );
}
Example #25
Source File: App.tsx    From netless-app with MIT License 4 votes vote down vote up
App: FunctionalComponent<AppProps> = memo(({ context, storage }) => {
  const [isWritable, setWritable] = useState(() => context.getIsWritable());
  const [countdownSecs, setCountdownSecs] = useState(0);
  const [startTime, setStartTime] = useState(0);
  const [paused, setPaused] = useState(false);

  const started = startTime > 0;

  const onStart = useCallback(() => {
    if (context.getIsWritable()) {
      setPaused(false);
      setStartTime(Math.floor(Date.now() / 1000));
    }
  }, [context]);

  const onPause = useCallback(() => {
    if (context.getIsWritable()) {
      setPaused(true);
    }
  }, [context]);

  const onResume = useCallback(() => {
    if (context.getIsWritable()) {
      setPaused(false);
    }
  }, [context]);

  const onReset = useCallback(() => {
    if (context.getIsWritable()) {
      setPaused(false);
      setCountdownSecs(0);
      setStartTime(0);
    }
  }, [context]);

  const onAdjustTime = useCallback(
    (adjustment: number) => {
      if (!started) {
        setCountdownSecs(countdownSecs => {
          if (!context.getIsWritable()) {
            return countdownSecs;
          }
          const minutes = Math.floor(countdownSecs / 60);
          const seconds = countdownSecs - minutes * 60;
          const min1 = Math.floor(minutes / 10);
          const min2 = minutes % 10;
          const sec1 = Math.floor(seconds / 10);
          const sec2 = seconds % 10;

          switch (adjustment) {
            case TimeAdjustment.AddTenMinutes: {
              const max = min2 + seconds === 0 ? 7 : 6;
              return (((min1 + 1) % max) * 10 + min2) * 60 + seconds;
            }
            case TimeAdjustment.ReduceTenMinutes: {
              const max = min2 + seconds === 0 ? 7 : 6;
              return (((min1 + max - 1) % max) * 10 + min2) * 60 + seconds;
            }
            case TimeAdjustment.AddOneMinute: {
              return (Math.min(5, min1) * 10 + ((min2 + 1) % 10)) * 60 + seconds;
            }
            case TimeAdjustment.ReduceOneMinute: {
              return (Math.min(5, min1) * 10 + ((min2 + 10 - 1) % 10)) * 60 + seconds;
            }
            case TimeAdjustment.AddTenSeconds: {
              return minutes * 60 + (((sec1 + 1) % 6) * 10 + sec2);
            }
            case TimeAdjustment.ReduceTenSeconds: {
              return minutes * 60 + (((sec1 + 6 - 1) % 6) * 10 + sec2);
            }
            case TimeAdjustment.AddOneSecond: {
              return minutes * 60 + (Math.min(5, sec1) * 10 + ((sec2 + 1) % 10));
            }
            case TimeAdjustment.ReduceOneSecond: {
              return minutes * 60 + (Math.min(5, sec1) * 10 + ((sec2 + 10 - 1) % 10));
            }
            default: {
              return countdownSecs;
            }
          }
        });
      }
    },
    [started, context]
  );

  useEffect(() => {
    setWritable(context.getIsWritable());
    context.emitter.on("writableChange", setWritable);
    return () => context.emitter.off("writableChange", setWritable);
  }, [context]);

  useEffect(() => {
    const handler: StorageStateChangedListener<StorageState> = diff => {
      if (diff.countdownSecs) {
        setCountdownSecs(storage.state.countdownSecs);
      }
      if (diff.paused) {
        setPaused(storage.state.paused);
      }
      if (diff.startTime) {
        setStartTime(storage.state.startTime);
      }
    };
    storage.onStateChanged.addListener(handler);
    return () => storage.onStateChanged.removeListener(handler);
  }, [storage]);

  useEffect(() => {
    if (context.getIsWritable()) {
      // unchanged values will be skipped automatically
      storage.setState({ countdownSecs, paused, startTime });
    }
  }, [countdownSecs, paused, startTime, context]);

  return (
    <Countdown
      readonly={!isWritable}
      countdownSecs={countdownSecs}
      startTime={startTime}
      paused={paused}
      onAdjustTime={onAdjustTime}
      onStart={onStart}
      onPause={onPause}
      onResume={onResume}
      onReset={onReset}
    />
  );
})
Example #26
Source File: Field.tsx    From adyen-web with MIT License 4 votes vote down vote up
Field: FunctionalComponent<FieldProps> = props => {
    //
    const {
        children,
        className,
        classNameModifiers,
        dir,
        disabled,
        errorMessage,
        helper,
        inputWrapperModifiers,
        isCollatingErrors,
        isLoading,
        isValid,
        label,
        name,
        onBlur,
        onFieldBlur,
        onFocus,
        onFocusField,
        showValidIcon,
        useLabelElement,
        // Redeclare prop names to avoid internal clashes
        filled: propsFilled,
        focused: propsFocused
    } = props;

    const uniqueId = useRef(getUniqueId(`adyen-checkout-${name}`));

    const [focused, setFocused] = useState(false);
    const [filled, setFilled] = useState(false);

    // The means by which focussed/filled is set for securedFields
    if (propsFocused != null) setFocused(!!propsFocused);
    if (propsFilled != null) setFilled(!!propsFilled);

    // The means by which focussed/filled is set for other fields - this function is passed down to them and triggered
    const onFocusHandler = useCallback(
        (event: h.JSX.TargetedEvent<HTMLInputElement>) => {
            setFocused(true);
            onFocus?.(event);
        },
        [onFocus]
    );

    const onBlurHandler = useCallback(
        (event: h.JSX.TargetedEvent<HTMLInputElement>) => {
            setFocused(false);
            onBlur?.(event);
            // When we also need to fire a specific function when a field blurs
            onFieldBlur?.(event);
        },
        [onBlur, onFieldBlur]
    );

    const renderContent = useCallback(() => {
        return (
            <Fragment>
                {typeof label === 'string' && (
                    <span
                        className={classNames({
                            'adyen-checkout__label__text': true,
                            'adyen-checkout__label__text--error': errorMessage
                        })}
                    >
                        {label}
                    </span>
                )}

                {/*@ts-ignore - function is callable*/}
                {typeof label === 'function' && label()}

                {helper && <span className={'adyen-checkout__helper-text'}>{helper}</span>}
                <div
                    className={classNames([
                        'adyen-checkout__input-wrapper',
                        ...inputWrapperModifiers.map(m => `adyen-checkout__input-wrapper--${m}`)
                    ])}
                    dir={dir}
                >
                    {toChildArray(children).map(
                        (child: ComponentChild): ComponentChild => {
                            const childProps = {
                                isValid,
                                onFocusHandler,
                                onBlurHandler,
                                isInvalid: !!errorMessage,
                                ...(name && { uniqueId: uniqueId.current })
                            };
                            return cloneElement(child as VNode, childProps);
                        }
                    )}

                    {isLoading && (
                        <span className="adyen-checkout-input__inline-validation adyen-checkout-input__inline-validation--loading">
                            <Spinner size="small" />
                        </span>
                    )}

                    {isValid && showValidIcon !== false && (
                        <span className="adyen-checkout-input__inline-validation adyen-checkout-input__inline-validation--valid">
                            <Icon type="checkmark" />
                        </span>
                    )}

                    {errorMessage && (
                        <span className="adyen-checkout-input__inline-validation adyen-checkout-input__inline-validation--invalid">
                            <Icon type="field_error" />
                        </span>
                    )}
                </div>
                {errorMessage && typeof errorMessage === 'string' && errorMessage.length && (
                    <span
                        className={'adyen-checkout__error-text'}
                        id={`${uniqueId.current}${ARIA_ERROR_SUFFIX}`}
                        aria-hidden={isCollatingErrors ? 'true' : null}
                        aria-live={isCollatingErrors ? null : 'polite'}
                    >
                        {errorMessage}
                    </span>
                )}
            </Fragment>
        );
    }, [children, errorMessage, isLoading, isValid, label, onFocusHandler, onBlurHandler]);

    const LabelOrDiv = useCallback(({ onFocusField, focused, filled, disabled, name, uniqueId, useLabelElement, children }) => {
        const defaultWrapperProps = {
            onClick: onFocusField,
            className: classNames({
                'adyen-checkout__label': true,
                'adyen-checkout__label--focused': focused,
                'adyen-checkout__label--filled': filled,
                'adyen-checkout__label--disabled': disabled
            })
        };

        return useLabelElement ? (
            <label {...defaultWrapperProps} htmlFor={name && uniqueId}>
                {children}
            </label>
        ) : (
            <div {...defaultWrapperProps} role={'form'}>
                {children}
            </div>
        );
    }, []);

    /**
     * RENDER
     */
    return (
        <div
            className={classNames(
                'adyen-checkout__field',
                className,
                classNameModifiers.map(m => `adyen-checkout__field--${m}`),
                {
                    'adyen-checkout__field--error': errorMessage,
                    'adyen-checkout__field--valid': isValid
                }
            )}
        >
            <LabelOrDiv
                onFocusField={onFocusField}
                name={name}
                disabled={disabled}
                filled={filled}
                focused={focused}
                useLabelElement={useLabelElement}
                uniqueId={uniqueId.current}
            >
                {renderContent()}
            </LabelOrDiv>
        </div>
    );
}