Skip to content

Commit

Permalink
Move display name into authcontext and stop allowing 'anonymous cross…
Browse files Browse the repository at this point in the history
…harer' #289
  • Loading branch information
mdirolf committed Jun 8, 2021
1 parent eb4dd4d commit 466b377
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 105 deletions.
4 changes: 3 additions & 1 deletion app/components/AuthContext.tsx
Expand Up @@ -133,14 +133,16 @@ export function requiresAdmin<T>(WrappedComponent: React.ComponentType<T>) {
};
}

interface AuthContextValue {
export interface AuthContextValue {
user?: firebase.User;
notifications?: Array<NotificationT>;
isAdmin: boolean;
loading: boolean;
error?: string;
constructorPage?: ConstructorPageT;
prefs?: AccountPrefsT;
displayName?: string | null;
updateDisplayName?: (n: string) => Promise<void>;
}
export const AuthContext = createContext({
user: undefined,
Expand Down
6 changes: 6 additions & 0 deletions app/components/ClueMode.tsx
Expand Up @@ -542,6 +542,12 @@ export const ClueMode = ({ state, ...props }: ClueModeProps) => {
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 visibles to solvers who
have submitted a solution.
</p>
<h4 css={{ marginTop: '1em' }}>Contest prize</h4>
<p>
If the contest has a prize solvers can choose to include their
Expand Down
29 changes: 7 additions & 22 deletions app/components/Comments.tsx
Expand Up @@ -8,7 +8,7 @@ import { PartialBy, Comment, Direction } from '../lib/types';
import { Identicon } from './Icons';
import { timeString, pastDistanceToNow } from '../lib/utils';
import { Emoji } from './Emoji';
import { DisplayNameForm, getDisplayName } from './DisplayNameForm';
import { DisplayNameForm, useDisplayName } from './DisplayNameForm';
import { App, TimestampClass } from '../lib/firebaseWrapper';
import {
CommentForModerationT,
Expand Down Expand Up @@ -238,8 +238,6 @@ const CommentFlair = (props: CommentFlairProps) => {
};

interface CommentFormProps {
displayName: string;
setDisplayName: (name: string) => void;
username?: string;
puzzlePublishTime: number;
puzzleAuthorId: string;
Expand All @@ -258,6 +256,7 @@ const CommentForm = ({
...props
}: CommentFormProps & { onCancel?: () => void }) => {
const [commentText, setCommentText] = useState('');
const displayName = useDisplayName();
const [editingDisplayName, setEditingDisplayName] = useState(false);
const [submittedComment, setSubmittedComment] = useState<LocalComment | null>(
null
Expand All @@ -276,7 +275,7 @@ const CommentForm = ({
const comment: CommentForModerationT = {
c: textToSubmit,
a: props.user.uid,
n: props.displayName,
n: displayName || 'Anonymous Crossharer',
t: props.solveTime,
ch: props.didCheat,
do: props.downsOnly,
Expand Down Expand Up @@ -352,7 +351,7 @@ const CommentForm = ({
<div css={{ textAlign: 'right' }}>
<LengthView maxLength={COMMENT_LENGTH_LIMIT} value={commentText} />
</div>
{editingDisplayName ? (
{editingDisplayName || !displayName ? (
''
) : (
<>
Expand All @@ -376,7 +375,7 @@ const CommentForm = ({
<CommentFlair
hasGuestConstructor={props.hasGuestConstructor}
username={props.username}
displayName={props.displayName}
displayName={displayName}
userId={props.user.uid}
puzzleAuthorId={props.puzzleAuthorId}
solveTime={props.solveTime}
Expand Down Expand Up @@ -407,16 +406,9 @@ const CommentForm = ({
''
)}
</form>
{editingDisplayName ? (
{editingDisplayName || !displayName ? (
<>
<DisplayNameForm
user={props.user}
onChange={(s) => {
props.setDisplayName(s);
setEditingDisplayName(false);
}}
onCancel={() => setEditingDisplayName(false)}
/>
<DisplayNameForm onCancel={() => setEditingDisplayName(false)} />
</>
) : (
''
Expand Down Expand Up @@ -470,9 +462,6 @@ export const Comments = ({
}: CommentsProps): JSX.Element => {
const authContext = useContext(AuthContext);
const [toShow, setToShow] = useState<Array<CommentOrLocalComment>>(comments);
const [displayName, setDisplayName] = useState(
getDisplayName(authContext.user, authContext.constructorPage)
);

useEffect(() => {
if (!authContext.notifications?.length) {
Expand Down Expand Up @@ -575,8 +564,6 @@ export const Comments = ({
<CommentForm
{...props}
username={authContext.constructorPage?.i}
displayName={displayName}
setDisplayName={setDisplayName}
user={authContext.user}
/>
)}
Expand All @@ -592,8 +579,6 @@ export const Comments = ({
<CommentWithReplies
user={authContext.user}
constructorPage={authContext.constructorPage}
displayName={displayName}
setDisplayName={setDisplayName}
comment={a}
{...props}
/>
Expand Down
58 changes: 30 additions & 28 deletions app/components/DisplayNameForm.tsx
@@ -1,45 +1,45 @@
import { useState, useContext } from 'react';
import type firebase from 'firebase/app';
import { AuthContext } from './AuthContext';
import { ConstructorPageT } from '../lib/constructorPage';
import { App, ServerTimestamp } from '../lib/firebaseWrapper';
import { Button } from './Buttons';
import { useSnackbar } from './Snackbar';

export const getDisplayName = (
user: firebase.User | undefined,
constructorPage: ConstructorPageT | undefined
) => {
return constructorPage?.n || user?.displayName || 'Anonymous Crossharer';
export const useDisplayName = () => {
const ctx = useContext(AuthContext);
return ctx.displayName;
};

interface DisplayNameFormProps {
user: firebase.User;
onChange: (newName: string) => void;
onCancel?: () => void;
}

export const DisplayNameForm = ({
user,
onChange,
onCancel,
}: DisplayNameFormProps) => {
export const DisplayNameForm = ({ onCancel }: DisplayNameFormProps) => {
const ctx = useContext(AuthContext);
const db = App.firestore();
const [submitting, setSubmitting] = useState(false);
const { showSnackbar } = useSnackbar();

function sanitize(input: string) {
return input.replace(/[^0-9a-zA-Z ]/g, '');
const user = ctx.user;

function sanitize(input: string | null | undefined) {
return input && input.replace(/[^0-9a-zA-Z ]/g, '');
}

const [newDisplayName, setNewDisplayName] = useState(
sanitize(getDisplayName(user, ctx.constructorPage))
sanitize(ctx.displayName)
);

if (!user) {
return <>Must be logged in</>;
}

const db = App.firestore();

const handleSubmit = (e: React.FormEvent) => {
setSubmitting(true);
e.preventDefault();
const toSubmit = newDisplayName.trim();
if (toSubmit) {
const updates = [user.updateProfile({ displayName: toSubmit })];
const toSubmit = newDisplayName?.trim();
if (toSubmit && ctx.updateDisplayName) {
const updates = [ctx.updateDisplayName(toSubmit)];
if (ctx.constructorPage) {
updates.push(
db
Expand All @@ -50,26 +50,28 @@ export const DisplayNameForm = ({
}
Promise.all(updates).then(() => {
setSubmitting(false);
if (!user.displayName) {
throw new Error('something went wrong');
}
onChange(user.displayName);
showSnackbar('Display name updated');
onCancel?.();
});
}
};

return (
<form onSubmit={handleSubmit}>
<label>
Update display name:
{ctx.displayName ? 'Update display name:' : 'Set your display name:'}
<input
css={{ margin: '0 0.5em' }}
type="text"
value={newDisplayName}
value={newDisplayName || ''}
onChange={(e) => setNewDisplayName(sanitize(e.target.value))}
/>
</label>
<Button type="submit" text="Save" disabled={submitting} />
<Button
type="submit"
text="Save"
disabled={submitting || !newDisplayName?.trim()}
/>
{onCancel ? (
<Button
boring={true}
Expand Down
30 changes: 7 additions & 23 deletions app/components/MetaSubmission.tsx
@@ -1,6 +1,6 @@
import { FormEvent, useContext, useState, Dispatch } from 'react';
import { AuthContext } from './AuthContext';
import { getDisplayName, DisplayNameForm } from './DisplayNameForm';
import { DisplayNameForm, useDisplayName } from './DisplayNameForm';
import { GoogleLinkButton, GoogleSignInButton } from './GoogleButtons';
import type firebase from 'firebase/app';
import { Button, ButtonAsLink } from './Buttons';
Expand All @@ -16,10 +16,9 @@ export const MetaSubmissionForm = (props: {
hasPrize: boolean;
dispatch: Dispatch<ContestSubmitAction>;
solutions: Array<string>;
displayName: string;
}) => {
const [submission, setSubmission] = useState('');
const [displayName, setDisplayName] = useState(props.displayName);
const displayName = useDisplayName();
const [editingDisplayName, setEditingDisplayName] = useState(false);
const [enteringForPrize, setEnteringForPrize] = useState(false);
const { addToast } = useSnackbar();
Expand All @@ -29,7 +28,7 @@ export const MetaSubmissionForm = (props: {
props.dispatch({
type: 'CONTESTSUBMIT',
submission: submission,
displayName: props.displayName,
displayName: displayName || 'Anonymous Crossharer',
...(props.hasPrize &&
enteringForPrize &&
props.user.email && { email: props.user.email }),
Expand Down Expand Up @@ -81,7 +80,7 @@ export const MetaSubmissionForm = (props: {
) : (
''
)}
{editingDisplayName ? (
{editingDisplayName || !displayName ? (
''
) : (
<>
Expand All @@ -102,16 +101,9 @@ export const MetaSubmissionForm = (props: {
</>
)}
</form>
{editingDisplayName ? (
{editingDisplayName || !displayName ? (
<>
<DisplayNameForm
user={props.user}
onChange={(s) => {
setDisplayName(s);
setEditingDisplayName(false);
}}
onCancel={() => setEditingDisplayName(false)}
/>
<DisplayNameForm onCancel={() => setEditingDisplayName(false)} />
</>
) : (
''
Expand All @@ -127,10 +119,6 @@ export const MetaSubmission = (props: {
solutions: Array<string>;
}) => {
const authContext = useContext(AuthContext);
const displayName = getDisplayName(
authContext.user,
authContext.constructorPage
);

return (
<div css={{ marginTop: '1em' }}>
Expand Down Expand Up @@ -184,11 +172,7 @@ export const MetaSubmission = (props: {
This is a meta puzzle! Submit your solution to see if you got it,
view the leaderboard, and read or submit comments:
</p>
<MetaSubmissionForm
user={authContext.user}
displayName={displayName}
{...props}
/>
<MetaSubmissionForm user={authContext.user} {...props} />
</>
)}
</div>
Expand Down
25 changes: 7 additions & 18 deletions app/components/PublishOverlay.tsx
@@ -1,9 +1,8 @@
import { useState, useCallback, useContext, FormEvent, ReactNode } from 'react';
import { useState, useCallback, FormEvent, ReactNode } from 'react';
import NextJSRouter from 'next/router';
import type firebase from 'firebase/app';

import { AuthContext } from './AuthContext';
import { DisplayNameForm, getDisplayName } from './DisplayNameForm';
import { DisplayNameForm, useDisplayName } from './DisplayNameForm';
import { Overlay } from './Overlay';
import { Emoji } from './Emoji';
import { App, ServerTimestamp } from '../lib/firebaseWrapper';
Expand All @@ -18,13 +17,10 @@ export function PublishOverlay(props: {
user: firebase.User;
cancelPublish: () => void;
}) {
const { constructorPage } = useContext(AuthContext);
const [inProgress, setInProgress] = useState(false);
const [done, setDone] = useState(false);
const [editingDisplayName, setEditingDisplayName] = useState(false);
const [displayName, setDisplayName] = useState(
getDisplayName(props.user, constructorPage)
);
const displayName = useDisplayName();

const doPublish = useCallback(
(event: FormEvent) => {
Expand All @@ -42,7 +38,7 @@ export function PublishOverlay(props: {
hourAgo.setHours(hourAgo.getHours() - 1);
const toPublish = {
...props.toPublish,
n: displayName,
n: displayName || 'Anonymous Crossharer',
p: ServerTimestamp,
};

Expand Down Expand Up @@ -75,15 +71,8 @@ export function PublishOverlay(props: {
} else {
contents = (
<>
{editingDisplayName ? (
<DisplayNameForm
user={props.user}
onChange={(s) => {
setDisplayName(s);
setEditingDisplayName(false);
}}
onCancel={() => setEditingDisplayName(false)}
/>
{editingDisplayName || !displayName ? (
<DisplayNameForm onCancel={() => setEditingDisplayName(false)} />
) : (
<h3>
{props.toPublish.gc ? (
Expand Down Expand Up @@ -127,7 +116,7 @@ export function PublishOverlay(props: {

<Button
onClick={doPublish}
disabled={editingDisplayName}
disabled={editingDisplayName || !displayName}
text="Publish Puzzle"
/>
</>
Expand Down

0 comments on commit 466b377

Please sign in to comment.