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 |
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 |
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 |
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 |
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'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'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}: "{str}"
</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'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 |
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
)}¤cy_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 |
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 |
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
)}¤cy_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>
) : (
''
)}
</>
);
}