react-icons/fa#FaInfoCircle TypeScript Examples

The following examples show how to use react-icons/fa#FaInfoCircle. 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: Toast.tsx    From convoychat with GNU General Public License v3.0 6 votes vote down vote up
toast = {
  error: (message: string) => {
    notify.addNotification({
      type: "warning",
      content: (
        <Toast type="warning" icon={FaExclamationCircle}>
          {message}
        </Toast>
      ),
      ...(defaultConfig as any),
    });
  },
  success: (message: string) => {
    notify.addNotification({
      type: "success",
      content: (
        <Toast type="success" icon={FaCheckCircle}>
          {message}
        </Toast>
      ),
      ...(defaultConfig as any),
    });
  },
  info: (message: string) => {
    notify.addNotification({
      type: "info",
      content: (
        <Toast type="info" icon={FaInfoCircle}>
          {message}
        </Toast>
      ),
      ...(defaultConfig as any),
    });
  },
}
Example #2
Source File: AboutButton.tsx    From calendar-hack with MIT License 6 votes vote down vote up
HomeButton: React.FC<Props> = () => {
    const themeContext = useContext(ThemeContext);
    return (
        <IconContext.Provider value={{ color: themeContext.colors.buttonIcons }}>
            <Root>
                <Link href="./about">
                    <FaInfoCircle style={{ verticalAlign: 'middle' }} />
                </Link>
            </Root>
        </IconContext.Provider>
    )
}
Example #3
Source File: index.tsx    From netflix-clone with GNU General Public License v3.0 5 votes vote down vote up
FeaturedMovie: React.FC<{ movieId: number }> = ({ movieId }) => {
  const [movie, setMovie] = useState<MovieProps>({} as MovieProps);

  useEffect(() => {
    api
      .get(
        `/tv/${movieId}?language=pt-BR&api_key=${process.env.REACT_APP_API_KEY}`,
      )
      .then(response => {
        const { data } = response;
        const seasons = `${data.number_of_seasons} Temporada${
          data.number_of_seasons > 1 ? 's' : ''
        }`;
        const rating = data.vote_average.toString();

        const ratingFormatted = rating.includes('.')
          ? rating.replace('.', '')
          : rating.concat('0');

        setMovie({
          id: data.id,
          name: data.original_name,
          imageUrl: `https://image.tmdb.org/t/p/original${data.backdrop_path}`,
          rating: ratingFormatted,
          releaseDate: data.first_air_date.toString().split('-')[0],
          seasons,
          overview: data.overview,
          genres: data.genres.map((genre: any) => genre.name).join(', '),
        });
      });
  }, [movieId]);

  return (
    <Container>
      {movie.id && (
        <MovieBackground image={movie.imageUrl}>
          <div>
            <Content>
              <h1>{movie.name}</h1>
              <MovieInfo>
                <span>{movie.rating}% relevante</span>
                <span>{movie.releaseDate}</span>
                <span>{movie.seasons || movie.runtime}</span>
              </MovieInfo>
              <p>{movie.overview}</p>
              <span>
                <strong>Gêneros:</strong> {movie.genres}
              </span>
              <MovieButtons>
                <MovieButtonPlay href="/">
                  <FaPlay /> Assistir
                </MovieButtonPlay>
                <MovieButtonMoreAbout href="/">
                  <FaInfoCircle /> Mais informações
                </MovieButtonMoreAbout>
              </MovieButtons>
            </Content>
          </div>
        </MovieBackground>
      )}
    </Container>
  );
}
Example #4
Source File: ClueMode.tsx    From crosshare with GNU Affero General Public License v3.0 4 votes vote down vote up
ClueMode = ({ state, ...props }: ClueModeProps) => {
  const [settingCoverPic, setSettingCoverPic] = useState(false);
  const [contestAnswerInProg, setContestAnswerInProg] = useState('');
  const [addingAlternate, setAddingAlternate] = useState(false);
  const [editingTags, setEditingTags] = useState(false);
  const privateUntil = state.isPrivateUntil?.toDate();
  const autoTags = autoTag(state);

  const contestAnswerError =
    state.contestAnswers &&
    isMetaSolution(contestAnswerInProg, state.contestAnswers)
      ? 'Duplicate solution!'
      : '';

  const count: Record<string, number> = {};

  const clueRows = props.completedEntries
    .sort((e1, e2) =>
      e1.direction === e2.direction
        ? e1.labelNumber - e2.labelNumber
        : e1.direction - e2.direction
    )
    .map((e) => {
      const clueIdx = count[e.completedWord || ''] || 0;
      count[e.completedWord || ''] = clueIdx + 1;
      return (
        <ClueRow
          idx={clueIdx}
          key={(e.completedWord || '') + clueIdx}
          dispatch={props.dispatch}
          entry={e}
          clues={props.clues}
        />
      );
    });

  if (addingAlternate) {
    return (
      <>
        <AlternateSolutionEditor
          grid={state.grid.cells}
          save={async (alt) => {
            const act: AddAlternateAction = {
              type: 'ADDALT',
              alternate: alt,
            };
            props.dispatch(act);
          }}
          cancel={() => setAddingAlternate(false)}
          width={state.grid.width}
          height={state.grid.height}
          highlight={state.grid.highlight}
          highlighted={state.grid.highlighted}
          hidden={state.grid.hidden}
          vBars={state.grid.vBars}
          hBars={state.grid.hBars}
        />
      </>
    );
  }
  return (
    <>
      <TopBar>
        <TopBarLink
          icon={<FaRegNewspaper />}
          text="Publish"
          onClick={() => {
            const a: PublishAction = {
              type: 'PUBLISH',
              publishTimestamp: TimestampClass.now(),
            };
            props.dispatch(a);
          }}
        />
        <TopBarLink
          icon={<SpinnerFinished />}
          text="Back to Grid"
          onClick={props.exitClueMode}
        />
      </TopBar>
      {state.toPublish ? (
        <PublishOverlay
          id={state.id}
          toPublish={state.toPublish}
          warnings={state.publishWarnings}
          user={props.user}
          cancelPublish={() => props.dispatch({ type: 'CANCELPUBLISH' })}
        />
      ) : (
        ''
      )}
      {state.publishErrors.length ? (
        <Overlay
          closeCallback={() => props.dispatch({ type: 'CLEARPUBLISHERRORS' })}
        >
          <>
            <div>Please fix the following errors and try publishing again:</div>
            <ul>
              {state.publishErrors.map((s, i) => (
                <li key={i}>{s}</li>
              ))}
            </ul>
            {state.publishWarnings.length ? (
              <>
                <div>Warnings:</div>
                <ul>
                  {state.publishWarnings.map((s, i) => (
                    <li key={i}>{s}</li>
                  ))}
                </ul>
              </>
            ) : (
              ''
            )}
          </>
        </Overlay>
      ) : (
        ''
      )}

      <div css={{ padding: '1em' }}>
        {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
        <label css={{ width: '100%' }}>
          <h2>Title</h2>
          <LengthLimitedInput
            type="text"
            css={{ width: '100%' }}
            placeholder="Give your puzzle a title"
            value={props.title || ''}
            maxLength={MAX_STRING_LENGTH}
            updateValue={(s: string) => {
              const sta: SetTitleAction = {
                type: 'SETTITLE',
                value: s,
              };
              props.dispatch(sta);
            }}
          />
          <div css={{ textAlign: 'right' }}>
            <LengthView
              maxLength={MAX_STRING_LENGTH}
              value={props.title || ''}
              hideUntilWithin={30}
            />
          </div>
        </label>
        <h2 css={{ marginTop: '1em' }}>Metadata</h2>
        <div>
          <ButtonAsLink
            onClick={() => setSettingCoverPic(true)}
            text="Add/edit cover pic"
          />
        </div>
        {settingCoverPic ? (
          <ImageCropper
            targetSize={COVER_PIC}
            isCircle={false}
            storageKey={`/users/${props.authorId}/${props.puzzleId}/cover.jpg`}
            cancelCrop={() => setSettingCoverPic(false)}
          />
        ) : (
          ''
        )}
        {props.notes !== null ? (
          <>
            <h3>Note:</h3>
            <LengthLimitedInput
              type="text"
              css={{ width: '100%' }}
              placeholder="Add a note"
              value={props.notes}
              maxLength={MAX_STRING_LENGTH}
              updateValue={(s: string) => {
                const sta: SetNotesAction = {
                  type: 'SETNOTES',
                  value: s,
                };
                props.dispatch(sta);
              }}
            />
            <div css={{ textAlign: 'right' }}>
              <LengthView
                maxLength={MAX_STRING_LENGTH}
                value={props.notes}
                hideUntilWithin={30}
              />
            </div>
            <p>
              <ButtonAsLink
                text="Remove note"
                onClick={() => {
                  const sna: SetNotesAction = { type: 'SETNOTES', value: null };
                  props.dispatch(sna);
                }}
              />
            </p>
          </>
        ) : (
          <div>
            <ButtonAsLink
              text="Add a note"
              onClick={() => {
                const sna: SetNotesAction = { type: 'SETNOTES', value: '' };
                props.dispatch(sna);
              }}
            />
            <ToolTipText
              css={{ marginLeft: '0.5em' }}
              text={<FaInfoCircle />}
              tooltip="Notes are shown before a puzzle is started and should be used if you need a short explainer of the theme or how the puzzle works"
            />
          </div>
        )}
        {props.blogPost !== null ? (
          <>
            <h3>Blog Post:</h3>
            <p>
              Blog posts are shown before solvers are finished with your puzzle.
              If you include spoilers you can hide them{' '}
              <code>||like this||</code>.
            </p>
            <LengthLimitedTextarea
              css={{ width: '100%', display: 'block' }}
              placeholder="Your post text (markdown format)"
              value={props.blogPost}
              maxLength={MAX_BLOG_LENGTH}
              updateValue={(s: string) => {
                const sta: SetBlogPostAction = {
                  type: 'SETBLOGPOST',
                  value: s,
                };
                props.dispatch(sta);
              }}
            />
            <div css={{ textAlign: 'right' }}>
              <LengthView
                maxLength={MAX_BLOG_LENGTH}
                value={props.blogPost || ''}
                hideUntilWithin={30}
              />
            </div>
            <p>
              <MarkdownPreview markdown={props.blogPost} />
              <ButtonAsLink
                text="Remove blog post"
                onClick={() => {
                  const sna: SetBlogPostAction = {
                    type: 'SETBLOGPOST',
                    value: null,
                  };
                  props.dispatch(sna);
                }}
              />
            </p>
          </>
        ) : (
          <div>
            <ButtonAsLink
              text="Add a blog post"
              onClick={() => {
                const sna: SetBlogPostAction = {
                  type: 'SETBLOGPOST',
                  value: '',
                };
                props.dispatch(sna);
              }}
            />
            <ToolTipText
              css={{ marginLeft: '0.5em' }}
              text={<FaInfoCircle />}
              tooltip="Blog posts are shown before and after the puzzle is solved - describe how you came up with the puzzle, talk about your day, whatever you want!"
            />
          </div>
        )}
        <div css={{ marginTop: '1em' }}>
          <label>
            <input
              css={{ marginRight: '1em' }}
              type="checkbox"
              checked={props.guestConstructor !== null}
              onChange={(e) => {
                const spa: SetGuestConstructorAction = {
                  type: 'SETGC',
                  value: e.target.checked ? '' : null,
                };
                props.dispatch(spa);
              }}
            />{' '}
            This puzzle is by a guest constructor
          </label>
        </div>
        {props.guestConstructor !== null ? (
          <div css={{ marginLeft: '1.5em', marginBottom: '1em' }}>
            <LengthLimitedInput
              type="text"
              css={{ width: '100%' }}
              placeholder="Guest constructor's name"
              value={props.guestConstructor}
              maxLength={MAX_STRING_LENGTH}
              updateValue={(s: string) => {
                const sta: SetGuestConstructorAction = {
                  type: 'SETGC',
                  value: s,
                };
                props.dispatch(sta);
              }}
            />
            <div css={{ textAlign: 'right' }}>
              <LengthView
                maxLength={MAX_STRING_LENGTH}
                value={props.guestConstructor}
                hideUntilWithin={30}
              />
            </div>
          </div>
        ) : (
          ''
        )}
        <div>
          <label>
            <input
              css={{ marginRight: '1em' }}
              type="checkbox"
              checked={state.isContestPuzzle}
              onChange={(e) => {
                const spa: UpdateContestAction = {
                  type: 'CONTEST',
                  enabled: e.target.checked,
                };
                props.dispatch(spa);
              }}
            />{' '}
            This is a meta/contest puzzle{' '}
            <ToolTipText
              css={{ marginLeft: '0.5em' }}
              text={<FaInfoCircle />}
              tooltip="A meta puzzle has an extra puzzle embedded in the grid for after solvers have finished solving. Solvers can submit their solution, find out if they were right or wrong, and view a leaderboard of those who've solved the contest correctly."
            />
          </label>
        </div>
        {state.isContestPuzzle ? (
          <div css={{ marginLeft: '1.5em', marginBottom: '1em' }}>
            <h4>Contest prompt (required):</h4>
            <p>
              Use the notes field above to give solvers a prompt for the
              contest.
            </p>
            <form
              onSubmit={(e) => {
                e.preventDefault();
                const spa: UpdateContestAction = {
                  type: 'CONTEST',
                  addAnswer: contestAnswerInProg,
                };
                props.dispatch(spa);
                setContestAnswerInProg('');
              }}
            >
              <h4>
                Contest solution(s) - must specify at least one valid solution:
              </h4>
              <p>
                Submissions will match regardless of case, whitespace, and
                punctuation.
              </p>
              {state.contestAnswers?.length ? (
                <ul>
                  {state.contestAnswers.map((a) => (
                    <li key={a}>
                      {a} (
                      <ButtonAsLink
                        onClick={() => {
                          const spa: UpdateContestAction = {
                            type: 'CONTEST',
                            removeAnswer: a,
                          };
                          props.dispatch(spa);
                        }}
                        text="remove"
                      />
                      )
                    </li>
                  ))}
                </ul>
              ) : (
                ''
              )}
              <LengthLimitedInput
                type="text"
                placeholder="Solution"
                value={contestAnswerInProg}
                updateValue={setContestAnswerInProg}
                maxLength={MAX_META_SUBMISSION_LENGTH}
              />
              <LengthView
                maxLength={MAX_META_SUBMISSION_LENGTH}
                value={contestAnswerInProg}
                hideUntilWithin={30}
              />
              {contestAnswerError ? (
                <span css={{ color: 'var(--error)', margin: 'auto 0.5em' }}>
                  {contestAnswerError}
                </span>
              ) : (
                ''
              )}
              <Button
                type="submit"
                css={{ marginLeft: '0.5em' }}
                disabled={
                  contestAnswerError !== '' ||
                  contestAnswerInProg.trim().length === 0
                }
                text="Add Solution"
              />
            </form>
            <h4 css={{ marginTop: '1em' }}>Contest explanation</h4>
            <p>
              After publishing, you can use a comment to explain how the
              meta/contest works - comments are only visible to solvers who have
              submitted or revealed the correct solution.
            </p>
            <h4 css={{ marginTop: '1em' }}>Delay before allowing reveal</h4>
            <p>
              Solvers get unlimited submission attempts and can optionally
              reveal the answer if they aren&apos;t able to figure it out. You
              can set a delay so that the reveal function will not be available
              until 1 week after the publish date.
            </p>
            <div>
              <label>
                <input
                  css={{ marginRight: '1em' }}
                  type="checkbox"
                  checked={
                    state.contestRevealDelay
                      ? state.contestRevealDelay > 0
                      : false
                  }
                  onChange={(e) => {
                    const spa: UpdateContestAction = {
                      type: 'CONTEST',
                      revealDelay: e.target.checked
                        ? 1000 * 60 * 60 * 24 * 7
                        : null,
                    };
                    props.dispatch(spa);
                  }}
                />{' '}
                Delay one week from publish date before allowing reveals
              </label>
            </div>

            <h4 css={{ marginTop: '1em' }}>Contest prize</h4>
            <p>
              If the contest has a prize solvers can choose to include their
              email address in their submission to be eligible to win.
            </p>
            <div>
              <label>
                <input
                  css={{ marginRight: '1em' }}
                  type="checkbox"
                  checked={state.contestHasPrize}
                  onChange={(e) => {
                    const spa: UpdateContestAction = {
                      type: 'CONTEST',
                      hasPrize: e.target.checked,
                    };
                    props.dispatch(spa);
                  }}
                />{' '}
                This contest has a prize
              </label>
            </div>
          </div>
        ) : (
          ''
        )}
        <div>
          <label>
            <input
              css={{ marginRight: '1em' }}
              type="checkbox"
              checked={state.isPrivate}
              onChange={(e) => {
                const spa: SetPrivateAction = {
                  type: 'SETPRIVATE',
                  value: e.target.checked,
                };
                props.dispatch(spa);
              }}
            />{' '}
            This puzzle is private
            <ToolTipText
              css={{ marginLeft: '0.5em' }}
              text={<FaInfoCircle />}
              tooltip="Private puzzles are still visible to anybody you share the link with. They do not appear on your constructor blog, they aren't eligible to be featured on the Crosshare homepage, and your followers won't be notified when they are published."
            />
          </label>
        </div>
        <div>
          <label>
            <input
              css={{ marginRight: '1em' }}
              type="checkbox"
              checked={state.isPrivateUntil !== null}
              onChange={(e) => {
                const spa: SetPrivateAction = {
                  type: 'SETPRIVATE',
                  value: e.target.checked && TimestampClass.now(),
                };
                props.dispatch(spa);
              }}
            />{' '}
            This puzzle should be private until a specified date/time
            <ToolTipText
              css={{ marginLeft: '0.5em' }}
              text={<FaInfoCircle />}
              tooltip="The puzzle won't appear on your constructor blog and your followers won't be notified until after the specified time."
            />
          </label>
          {privateUntil ? (
            <p css={{ marginLeft: '1.5em' }}>
              Visible after {lightFormat(privateUntil, "M/d/y' at 'h:mma")}:
              <DateTimePicker
                picked={privateUntil}
                setPicked={(d) => {
                  const spa: SetPrivateAction = {
                    type: 'SETPRIVATE',
                    value: TimestampClass.fromDate(d),
                  };
                  props.dispatch(spa);
                }}
              />
            </p>
          ) : (
            ''
          )}
        </div>
        <h3>Tags</h3>
        <p>
          Tags are shown any time a puzzle is displayed on the site, and help
          solvers quickly find puzzles with a particular attribute or theme.
        </p>
        {editingTags ? (
          <div css={{ marginBottom: '1.5em' }}>
            <TagEditor
              userTags={state.userTags}
              autoTags={autoTags}
              cancel={() => setEditingTags(false)}
              save={async (newTags) => {
                const st: SetTagsAction = {
                  type: 'SETTAGS',
                  tags: newTags,
                };
                props.dispatch(st);
                setEditingTags(false);
              }}
            />
          </div>
        ) : (
          <>
            <h4>Current tags:</h4>
            <TagList tags={state.userTags.concat(autoTags)} />
            <p>
              <Button onClick={() => setEditingTags(true)} text="Edit Tags" />
            </p>
          </>
        )}

        <h2 css={{ marginTop: '1em' }}>Clues</h2>
        {props.completedEntries.length ? (
          <table css={{ width: '100%' }}>
            <tbody>{clueRows}</tbody>
          </table>
        ) : (
          <>
            <p>
              This where you come to set clues for your puzzle, but you
              don&apos;t have any completed fill words yet!
            </p>
            <p>
              Go back to{' '}
              <ButtonAsLink
                text="the grid"
                onClick={(e) => {
                  props.exitClueMode();
                  e.preventDefault();
                }}
              />{' '}
              and fill in one or more words completely. Then come back here and
              make some clues.
            </p>
          </>
        )}
        <h2 css={{ marginTop: '1em' }}>Advanced</h2>
        <div>
          {state.alternates.length ? (
            <>
              <h3>Alternate Solutions</h3>
              <ul>
                {state.alternates.map((a, i) => (
                  <li key={i}>
                    {Object.entries(a).map(([pos, str]) => (
                      <span
                        css={{ '& + &:before': { content: '", "' } }}
                        key={pos}
                      >
                        Cell {pos}: &quot;{str}&quot;
                      </span>
                    ))}{' '}
                    (
                    <ButtonAsLink
                      onClick={() => {
                        const delAlt: DelAlternateAction = {
                          type: 'DELALT',
                          alternate: a,
                        };
                        props.dispatch(delAlt);
                      }}
                      text="remove"
                    />
                    )
                  </li>
                ))}
              </ul>
            </>
          ) : (
            ''
          )}
          <ButtonAsLink
            disabled={!state.gridIsComplete}
            text="Add an alternate solution"
            onClick={() => {
              setAddingAlternate(true);
            }}
          />
          <ToolTipText
            css={{ marginLeft: '0.5em' }}
            text={<FaInfoCircle />}
            tooltip={
              <>
                <p>
                  Alternate solutions can be used if one or more entries in your
                  puzzle have multiple valid solutions (e.g. a
                  Schrödinger&apos;s puzzle or a puzzle with bi-directional
                  rebuses).
                </p>
                <p>
                  Alternates can only be added once the grid is completely
                  filled. Once an alternate has been added the grid cannot be
                  further edited unless all alternates are deleted.
                </p>
              </>
            }
          />
        </div>
      </div>
    </>
  );
}
Example #5
Source File: ConstructorPage.tsx    From crosshare with GNU Affero General Public License v3.0 4 votes vote down vote up
ConstructorPage = (props: ConstructorPageProps) => {
  const { locale } = useRouter();
  const { isAdmin } = useContext(AuthContext);
  const [showOverlay, setShowOverlay] = useState(false);
  const [overlayIsFollowing, setOverlayIsFollowing] = useState(false);
  const coverPic = props.coverPicture;
  const profilePic = props.profilePicture;
  const username = props.constructor.i || props.constructor.id;
  const description =
    'The latest crossword puzzles from ' +
    props.constructor.n +
    ' (@' +
    username +
    '). ' +
    props.constructor.b;
  const title =
    props.constructor.n + ' (@' + username + ') | Crosshare Crossword Puzzles';
  const paypalEmail = props.constructor.pp;
  const paypalText = props.constructor.pt;
  const loc = locale || 'en';

  return (
    <>
      <Head>
        <title>{title}</title>
        <meta key="og:title" property="og:title" content={title} />
        <meta
          key="og:description"
          property="og:description"
          content={description}
        />
        {profilePic ? (
          <>
            <meta key="og:image" property="og:image" content={profilePic} />
            <meta
              key="og:image:width"
              property="og:image:width"
              content="200"
            />
            <meta
              key="og:image:height"
              property="og:image:height"
              content="200"
            />
          </>
        ) : (
          ''
        )}
        <meta key="description" name="description" content={description} />
        <link
          rel="alternate"
          type="application/rss+xml"
          title={title}
          href={`https://crosshare.org/api/feed/${username}`}
        />
        <I18nTags
          locale={loc}
          canonicalPath={`/${username}${
            props.currentPage !== 0 ? '/page/' + props.currentPage : ''
          }`}
        />
        {props.prevPage === 0 ? (
          <link
            rel="prev"
            href={`https://crosshare.org${
              loc == 'en' ? '' : '/' + loc
            }/${username}`}
          />
        ) : (
          ''
        )}
        {props.prevPage ? (
          <link
            rel="prev"
            href={`https://crosshare.org${
              loc == 'en' ? '' : '/' + loc
            }/${username}/page/${props.prevPage}`}
          />
        ) : (
          ''
        )}
        {props.nextPage !== null ? (
          <link
            rel="next"
            href={`https://crosshare.org${
              loc == 'en' ? '' : '/' + loc
            }/${username}/page/${props.nextPage}`}
          />
        ) : (
          ''
        )}
      </Head>
      <DefaultTopBar />
      {coverPic ? <CoverPic coverPicture={coverPic} /> : ''}
      <div
        css={{
          margin: '2em 1em',
          [HUGE_AND_UP]: {
            maxWidth: MAX_WIDTH,
            margin: '2em auto',
          },
        }}
      >
        <ProfilePicAndName
          coverImage={coverPic}
          profilePic={profilePic}
          topLine={
            <>
              {props.isPatron ? (
                <PatronIcon css={{ marginRight: '0.3em' }} linkIt={true} />
              ) : (
                ''
              )}
              {props.constructor.n}
            </>
          }
          byLine={
            <>
              <h2
                css={{
                  fontSize: '1em',
                  fontWeight: 'normal',
                  marginBottom: '0.25em',
                }}
              >
                <Link href={'/' + username}>@{username}</Link>
              </h2>
              {showOverlay ? (
                <Overlay closeCallback={() => setShowOverlay(false)}>
                  <div css={{ textAlign: 'center' }}>
                    {overlayIsFollowing ? (
                      <>
                        <h2>
                          <Trans id="following-count">
                            {props.following.length} Following
                          </Trans>
                        </h2>
                        <FollowersList
                          pages={props.following}
                          close={() => setShowOverlay(false)}
                        />
                      </>
                    ) : (
                      <>
                        <h2>
                          <Plural
                            id="follower-count"
                            value={props.followCount}
                            one="1 Follower"
                            other="# Followers"
                          />
                        </h2>
                        <h3>
                          <Plural
                            id="follower-blog-count"
                            value={props.followers.length}
                            one="1 with a Crosshare blog:"
                            other="# with Crosshare blogs:"
                          />
                        </h3>
                        <FollowersList
                          pages={props.followers}
                          close={() => setShowOverlay(false)}
                        />
                      </>
                    )}
                  </div>
                </Overlay>
              ) : (
                ''
              )}
              <p>
                <ButtonAsLink
                  disabled={props.following.length === 0}
                  onClick={() => {
                    setShowOverlay(true);
                    setOverlayIsFollowing(true);
                  }}
                  text={
                    <Trans id="following-count">
                      {props.following.length} Following
                    </Trans>
                  }
                />
                {' · '}
                <ButtonAsLink
                  disabled={props.followCount === 0}
                  onClick={() => {
                    setShowOverlay(true);
                    setOverlayIsFollowing(false);
                  }}
                  text={
                    <Plural
                      id="follower-count"
                      value={props.followCount}
                      one="1 Follower"
                      other="# Followers"
                    />
                  }
                />
              </p>
            </>
          }
        />
        <div css={{ textAlign: 'center', marginBottom: '1.5em' }}>
          <FollowButton page={props.constructor} />
        </div>
        <div css={{ marginBottom: '1.5em' }}>
          <Markdown text={props.constructor.b} />
          {paypalEmail && paypalText ? (
            <div>
              <LinkButtonSimpleA
                css={{ marginRight: '0.5em' }}
                href={`https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=${encodeURIComponent(
                  paypalEmail
                )}&item_name=${encodeURIComponent(
                  paypalText
                )}&currency_code=USD&source=url`}
                text={t({
                  message: `Tip ${props.constructor.n}`,
                  comment:
                    'The variable is the name of the user who will recieve the $ tip',
                })}
              />
              <ToolTipText
                text={<FaInfoCircle />}
                tooltip={t`All donations go directly to the constructor via PayPal`}
              />
            </div>
          ) : (
            ''
          )}
        </div>
        {props.puzzles.map((p, i) => (
          <PuzzleResultLink
            key={i}
            puzzle={p}
            showDate={true}
            showBlogPost={true}
            showAuthor={false}
            constructorIsPatron={props.isPatron}
            filterTags={[]}
          />
        ))}
        {props.nextPage || props.prevPage !== null ? (
          <p css={{ textAlign: 'center' }}>
            {props.prevPage === 0 ? (
              <Link css={{ marginRight: '2em' }} href={'/' + username}>
                ← <Trans>Newer Puzzles</Trans>
              </Link>
            ) : (
              ''
            )}
            {props.prevPage ? (
              <Link
                css={{ marginRight: '2em' }}
                href={'/' + username + '/page/' + props.prevPage}
              >
                ← <Trans>Newer Puzzles</Trans>
              </Link>
            ) : (
              ''
            )}
            {props.nextPage !== null ? (
              <Link href={'/' + username + '/page/' + props.nextPage}>
                <Trans>Older Puzzles</Trans> →
              </Link>
            ) : (
              ''
            )}
          </p>
        ) : (
          ''
        )}
        {isAdmin ? <ConstructorStats userId={props.constructor.u} /> : ''}
      </div>
    </>
  );
}
Example #6
Source File: FollowButton.tsx    From crosshare with GNU Affero General Public License v3.0 4 votes vote down vote up
FollowButton = ({ page, ...props }: { page: ConstructorPageT, className?: string }) => {
  const authCtx = useContext(AuthContext);
  const user = authCtx.user;
  const isFollowing = authCtx.prefs?.following?.includes(page.u);
  const { showSnackbar } = useSnackbar();
  const [showOverlay, setShowOverlay] = useState(false);
  const doFollow = useCallback(
    async (loggedInAs: firebase.User) => {
      const db = App.firestore();
      setShowOverlay(false);
      return Promise.all([
        db
          .doc(`prefs/${loggedInAs.uid}`)
          .set({ following: FieldValue.arrayUnion(page.u) }, { merge: true }),
        db
          .doc(`followers/${page.u}`)
          .set({ f: FieldValue.arrayUnion(loggedInAs.uid) }, { merge: true }),
      ]).then(() => {
        showSnackbar(t`You'll be notified when ${page.n} posts a new puzzle`);
      });
    },
    [page.n, page.u, showSnackbar]
  );

  const css = { minWidth: '7em' };

  if (!user || user.isAnonymous) {
    return (
      <>
        {showOverlay ? (
          <Overlay closeCallback={() => setShowOverlay(false)}>
            <div css={{ textAlign: 'center' }}>
              <h2>
                Follow {page.n} to get notified when they post a new puzzle
              </h2>
              <p>Login with Google to follow</p>
              {user ? (
                <GoogleLinkButton user={user} postSignIn={doFollow} />
              ) : (
                <GoogleSignInButton postSignIn={doFollow} />
              )}
            </div>
          </Overlay>
        ) : (
          ''
        )}
        <Button
          css={css}
          hollow
          disabled={authCtx.loading}
          onClick={(e) => { e.stopPropagation(); setShowOverlay(true); }}
          text={t`Follow`}
          {...props}
        />
      </>
    );
  }
  if (user.uid === page.u) {
    return (
      <>
        <Button css={css} hollow disabled text={<>{t`Follow`}
          <ToolTipText
            css={{ marginLeft: '0.5em' }}
            text={<FaInfoCircle />}
            tooltip={t`You can't follow yourself!`}
          />
        </>} {...props} />
      </>
    );
  }
  if (isFollowing) {
    const db = App.firestore();
    return (
      <>
        <Button
          css={css}
          onClick={(e) => {
            e.stopPropagation();
            return Promise.all([
              db
                .doc(`prefs/${user.uid}`)
                .set(
                  { following: FieldValue.arrayRemove(page.u) },
                  { merge: true }
                ),
              db
                .doc(`followers/${page.u}`)
                .set({ f: FieldValue.arrayRemove(user.uid) }, { merge: true }),
            ]).then(() => {
              showSnackbar(t`No longer following ${page.n}`);
            });
          }
          }
          text={t`Following`}
          hoverText={t`Unfollow`}
          hoverCSS={{ backgroundColor: 'var(--error)' }}
          {...props}
        />
      </>
    );
  }
  return <Button css={css} hollow onClick={(e) => { e.stopPropagation(); doFollow(user); }} text={t`Follow`} {...props} />;
}
Example #7
Source File: PuzzleHeading.tsx    From crosshare with GNU Affero General Public License v3.0 4 votes vote down vote up
PuzzleHeading = (props: {
  rating: GlickoScoreT | null;
  publishTime: number;
  showTip: boolean;
  isContest: boolean;
  constructorNotes: string | null;
  coverImage: string | null | undefined;
  profilePic: string | null | undefined;
  title: string;
  authorName: string;
  guestConstructor: string | null;
  constructorPage: ConstructorPageT | null;
  constructorIsPatron: boolean;
  blogPost: string | null;
  tags: string[];
}) => {
  const isEmbed = useContext(EmbedContext);

  const publishDate = new Date(props.publishTime);
  return (
    <>
      <ProfilePicAndName
        {...props}
        bonusMargin={1}
        topLine={props.title}
        byLine={
          <p css={{ overflowWrap: 'break-word' }}>
            <DifficultyBadge puzzleRating={props.rating} />
            {' · '}
            <AuthorLink
              authorName={props.authorName}
              constructorPage={props.constructorPage}
              guestConstructor={props.guestConstructor}
              showFollowButton={true}
              isPatron={props.constructorIsPatron}
            />
            {isEmbed ? (
              ''
            ) : (
              <>
                {' · '}
                <span title={publishDate.toISOString()}>
                  <Trans comment="The variable is a timestamp like '4 days ago' or 'hace 4 dias'">
                    Published <PastDistanceToNow date={publishDate} />
                  </Trans>
                </span>
              </>
            )}
          </p>
        }
      />
      <TagList
        css={{ justifyContent: 'center', fontSize: '0.9em' }}
        tags={props.tags}
        link
      />
      {props.constructorNotes ? (
        <div css={{ textAlign: 'center', overflowWrap: 'break-word' }}>
          <ConstructorNotes
            isContest={props.isContest}
            notes={props.constructorNotes}
          />
        </div>
      ) : (
        ''
      )}
      {props.blogPost ? (
        <div css={{ margin: '1em 0', overflowWrap: 'break-word' }}>
          <Markdown css={{ textAlign: 'left' }} text={props.blogPost} />
        </div>
      ) : (
        ''
      )}
      {props.constructorPage?.sig ? (
        <div css={{ margin: '1em 0', overflowWrap: 'break-word' }}>
          <Markdown
            inline={true}
            css={{ textAlign: 'left' }}
            text={props.constructorPage.sig}
          />
        </div>
      ) : (
        ''
      )}

      {props.showTip &&
      props.constructorPage?.pp &&
      props.constructorPage.pt ? (
        <div css={{ textAlign: 'center' }}>
          <LinkButtonSimpleA
            css={{ marginRight: '0.5em' }}
            href={`https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=${encodeURIComponent(
              props.constructorPage.pp
            )}&item_name=${encodeURIComponent(
              props.constructorPage.pt
            )}&currency_code=USD&source=url`}
            text={`Tip ${props.constructorPage.n}`}
          />
          <ToolTipText
            text={<FaInfoCircle />}
            tooltip={
              <Trans id="tip-hover">
                All donations go directly to the constructor via PayPal
              </Trans>
            }
          />
        </div>
      ) : (
        ''
      )}
    </>
  );
}