Skip to content

Commit

Permalink
[PR] Accept rules and policies (#354)
Browse files Browse the repository at this point in the history
* add step for rules and policy acceptance

* indicate spam folder should be checked

* only include certain fields in user.json

* chore: update missing email copy

* refactor: PlayKey interface

* chore: remove todo

* refactor: exhaust dependency list

* fix: rulesAccepted check

* fix: rules screen was not showing up correctly

* fix: completing rules accept during account creation

* chore: remove duplication

* refactor: use/refreshPlayKey => use/refreshUserData

* chore: update rule 3 copy

Co-authored-by: Nikhil Narayana <[email protected]>
  • Loading branch information
JLaferri and NikhilNarayana authored Dec 12, 2022
1 parent d66cb33 commit 4078a7e
Show file tree
Hide file tree
Showing 17 changed files with 325 additions and 75 deletions.
2 changes: 2 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const slippiHomepage = "https://slippi.gg";
export const slippiActivationUrl = "https://slippi.gg/online/enable";
export const slippiManagePage = "https://slippi.gg/manage";

export const currentRulesVersion = 1;

export const socials = {
twitterId: "ProjectSlippi",
discordUrl: "https://slippi.gg/discord",
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/containers/ActivateOnlineForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Typography from "@mui/material/Typography";
import React from "react";
import { Controller, useForm } from "react-hook-form";

import { useAccount, usePlayKey } from "@/lib/hooks/useAccount";
import { useAccount, useUserData } from "@/lib/hooks/useAccount";
import { useToasts } from "@/lib/hooks/useToasts";
import { validateConnectCodeStart } from "@/lib/validate";
import { useServices } from "@/services";
Expand All @@ -17,7 +17,7 @@ const log = window.electron.log;

export const ActivateOnlineForm: React.FC<{ onSubmit?: () => void }> = ({ onSubmit }) => {
const user = useAccount((store) => store.user);
const refreshActivation = usePlayKey();
const refreshActivation = useUserData();
return (
<div>
<div>Your connect code is used for players to connect with you directly.</div>
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/containers/Header/ActivateOnlineDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import React from "react";

import { usePlayKey } from "@/lib/hooks/useAccount";
import { useUserData } from "@/lib/hooks/useAccount";
import { useToasts } from "@/lib/hooks/useToasts";

import { ActivateOnlineForm } from "../ActivateOnlineForm";
Expand All @@ -20,11 +20,11 @@ export interface ActivateOnlineDialogProps {
export const ActivateOnlineDialog: React.FC<ActivateOnlineDialogProps> = ({ open, onClose, onSubmit }) => {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
const refreshPlayKey = usePlayKey();
const refreshUserData = useUserData();
const { showError } = useToasts();

const handleSubmit = () => {
refreshPlayKey()
refreshUserData()
.then(() => {
onClose();
onSubmit();
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/containers/Header/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const UserMenu: React.FC<{
handleError: (error: any) => void;
}> = ({ user, handleError }) => {
const { authService } = useServices();
const playKey = useAccount((store) => store.playKey);
const userData = useAccount((store) => store.userData);
const displayName = useAccount((store) => store.displayName);
const loading = useAccount((store) => store.loading);
const serverError = useAccount((store) => store.serverError);
Expand Down Expand Up @@ -54,7 +54,7 @@ export const UserMenu: React.FC<{
const generateMenuItems = (): IconMenuItem[] => {
const items: IconMenuItem[] = [];

if (!playKey && !serverError) {
if (!userData?.playKey && !serverError) {
items.push({
onClick: () => {
closeMenu();
Expand All @@ -65,7 +65,7 @@ export const UserMenu: React.FC<{
});
}

if (playKey) {
if (userData) {
items.push({
onClick: () => {
closeMenu();
Expand All @@ -90,7 +90,7 @@ export const UserMenu: React.FC<{
let errMessage: string | undefined = undefined;
if (serverError) {
errMessage = "Slippi server error";
} else if (!playKey) {
} else if (!userData?.playKey) {
errMessage = "Online activation required";
}

Expand All @@ -100,7 +100,7 @@ export const UserMenu: React.FC<{
<UserInfo
displayName={displayName}
displayPicture={user.displayPicture}
connectCode={playKey?.connectCode}
connectCode={userData?.playKey?.connectCode}
errorMessage={errMessage}
loading={loading}
/>
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/containers/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const Header: React.FC<HeaderProps> = ({ menuItems }) => {
const openModal = useLoginModal((store) => store.openModal);
const { open } = useSettingsModal();
const currentUser = useAccount((store) => store.user);
const playKey = useAccount((store) => store.playKey);
const userData = useAccount((store) => store.userData);
const serverError = useAccount((store) => store.serverError);
const meleeIsoPath = useSettings((store) => store.settings.isoPath) || undefined;
const { showError } = useToasts();
Expand All @@ -62,15 +62,15 @@ export const Header: React.FC<HeaderProps> = ({ menuItems }) => {
}

// Ensure user has a valid play key
if (!playKey && !serverError) {
if (!userData?.playKey && !serverError) {
setActivateOnlineModal(true);
return;
}

if (playKey) {
if (userData?.playKey) {
// Ensure the play key is saved to disk
try {
await slippiBackendService.assertPlayKey(playKey);
await slippiBackendService.assertPlayKey(userData.playKey);
} catch (err) {
showError(err);
return;
Expand All @@ -87,7 +87,7 @@ export const Header: React.FC<HeaderProps> = ({ menuItems }) => {

return;
},
[currentUser, launchNetplay, meleeIsoPath, playKey, serverError, showError, slippiBackendService],
[currentUser, launchNetplay, meleeIsoPath, userData, serverError, showError, slippiBackendService],
);

return (
Expand Down
177 changes: 177 additions & 0 deletions src/renderer/containers/QuickStart/AcceptRulesStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { colors } from "@common/colors";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { Button, Checkbox, CircularProgress, FormControlLabel, Typography } from "@mui/material";
import Box from "@mui/material/Box";
import React, { useState } from "react";

import { ExternalLink as A } from "@/components/ExternalLink";
import { useAccount, useUserData } from "@/lib/hooks/useAccount";
import { useToasts } from "@/lib/hooks/useToasts";
import { useServices } from "@/services";

import { QuickStartHeader } from "./QuickStartHeader";

const Container = styled.div`
margin: 0 auto;
width: 100%;
max-width: 800px;
`;

const classes = {
sectionHeader: css`
margin-top: 32px;
margin-bottom: 12px;
font-weight: bold;
`,
rulesContainer: css`
color: ${colors.textSecondary};
padding: 8px;
background-color: #00000040;
border-radius: 8px;
margin-bottom: 12px;
`,
rulesList: css`
margin-left: 8px;
margin-top: 8px;
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 8px;
gap: 8px;
`,
policiesList: css`
margin-left: 16px;
margin-top: 8px;
margin-bottom: 8px;
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 8px 14px;
gap: 8px 14px;
`,
button: css`
margin-top: 32px;
width: 150px;
height: 54px;
`,
link: css`
color: #b984bb;
`,
};

export const AcceptRulesStep: React.FC = () => {
const { slippiBackendService } = useServices();
const { showError } = useToasts();
const refreshUserData = useUserData();
const user = useAccount((store) => store.user);
const [rulesChecked, setRulesChecked] = useState(false);
const [policiesChecked, setPoliciesChecked] = useState(false);
const [processing, setProcessing] = useState(false);

const handleAcceptClick = async () => {
setProcessing(true);

try {
await slippiBackendService.acceptRules();
await refreshUserData();
} catch (err: any) {
showError(err.message);
} finally {
setProcessing(false);
}
};

// TODO: Only show slippi rules if rulesAccepted is null/0 ?

let stepBody = null;
if (user) {
stepBody = (
<>
<Typography css={classes.sectionHeader}>Slippi Online Rules</Typography>
<div css={classes.rulesContainer}>
<Typography>
These are a set of rules to follow when using Slippi. Breaking these rules may result in a suspension or ban
depending on severity and frequency. This is not an exhaustive list, we reserve the right to suspend or ban
an account for any reason.
</Typography>
<div css={classes.rulesList}>
<Typography>1.</Typography>
<Typography>
Racist, homophobic, transphobic, or otherwise bigoted names and codes are not allowed. Targeted harassment
in names and codes is also not allowed.
</Typography>
<Typography>2.</Typography>
<Typography>
Slippi does its best to promote fairness in terms of player matching, result reporting, etc. Attempting to
circumvent these systems is not allowed.
</Typography>
<Typography>3.</Typography>
<Typography>Intentionally manipulating the game performance for your own gain is not allowed.</Typography>
<Typography>4.</Typography>
<Typography>Macros and bots are not allowed.</Typography>
</div>
</div>
<FormControlLabel
label="Accept Slippi Rules"
control={
<Checkbox
checked={rulesChecked}
disabled={processing}
onChange={(_event, value) => setRulesChecked(value)}
sx={{ "& .MuiSvgIcon-root": { fontSize: 28 } }}
/>
}
/>
<Typography css={classes.sectionHeader}>Privacy Policy and Terms of Service</Typography>
<div css={classes.policiesList}>
<Typography color={colors.textSecondary}></Typography>
<Typography color={colors.textSecondary}>
Click to review the{" "}
<A css={classes.link} href="https://slippi.gg/privacy">
Slippi Privacy Policy
</A>
</Typography>
<Typography color={colors.textSecondary}></Typography>
<Typography color={colors.textSecondary}>
Click to review the{" "}
<A css={classes.link} href="https://slippi.gg/tos">
Slippi Terms of Service
</A>
</Typography>
</div>
<FormControlLabel
label="Accept Privacy Policy and Terms of Service"
control={
<Checkbox
checked={policiesChecked}
disabled={processing}
onChange={(_event, value) => setPoliciesChecked(value)}
sx={{ "& .MuiSvgIcon-root": { fontSize: 28 } }}
/>
}
/>
<div>
<Button
css={classes.button}
onClick={handleAcceptClick}
variant="contained"
disabled={!policiesChecked || !rulesChecked || processing}
size="large"
>
{processing ? <CircularProgress color="inherit" size={24} /> : "Accept All"}
</Button>
</div>
</>
);
} else {
stepBody = <div>An error occurred. The application does not have a user.</div>;
}

return (
<Box display="flex" flexDirection="column" flexGrow="1">
<Container>
<QuickStartHeader>Accept rules and policies</QuickStartHeader>
{stepBody}
</Container>
</Box>
);
};
11 changes: 6 additions & 5 deletions src/renderer/containers/QuickStart/VerifyEmailStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { useEffect } from "react";

import { ExternalLink as A } from "@/components/ExternalLink";
import { useAccount } from "@/lib/hooks/useAccount";
import { useToasts } from "@/lib/hooks/useToasts";
import { useServices } from "@/services";

import { QuickStartHeader } from "./QuickStartHeader";
Expand Down Expand Up @@ -69,14 +70,14 @@ const classes = {

export const VerifyEmailStep: React.FC = () => {
const { authService } = useServices();
const setServerError = useAccount((store) => store.setServerError);
const { showError } = useToasts();
const user = useAccount((store) => store.user);
const emailVerificationSent = useAccount((store) => store.emailVerificationSent);
const setEmailVerificationSent = useAccount((store) => store.setEmailVerificationSent);

const handleCheckVerification = async () => {
authService.refreshUser().catch((err) => {
setServerError(err.message);
showError(err.message);
});
};

Expand All @@ -86,14 +87,14 @@ export const VerifyEmailStep: React.FC = () => {
await authService.sendVerificationEmail();
setEmailVerificationSent(true);
} catch (err: any) {
setServerError(err.message);
showError(err.message);
}
};

if (user && !user.emailVerified && !emailVerificationSent) {
void sendVerificationEmail();
}
}, [emailVerificationSent, setEmailVerificationSent, setServerError, user, authService]);
}, [emailVerificationSent, setEmailVerificationSent, showError, user, authService]);

const preVerification = (
<>
Expand All @@ -104,7 +105,7 @@ export const VerifyEmailStep: React.FC = () => {
Check Verification
</Button>
<div css={classes.emailNotFoundContainer}>
Not finding email?{" "}
Can't find the email? Check your spam folder. Still missing?{" "}
<a href="#" onClick={authService.sendVerificationEmail}>
send again
</a>
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/containers/QuickStart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useMousetrap } from "@/lib/hooks/useMousetrap";
import { QuickStartStep } from "@/lib/hooks/useQuickStart";
import { platformTitleBarStyles } from "@/styles/platformTitleBarStyles";

import { AcceptRulesStep } from "./AcceptRulesStep";
import { ActivateOnlineStep } from "./ActivateOnlineStep";
import { ImportDolphinSettingsStep } from "./ImportDolphinSettingsStep";
import { IsoSelectionStep } from "./IsoSelectionStep";
Expand Down Expand Up @@ -39,6 +40,8 @@ const getStepContent = (step: QuickStartStep | null) => {
return <LoginStep />;
case QuickStartStep.VERIFY_EMAIL:
return <VerifyEmailStep />;
case QuickStartStep.ACCEPT_RULES:
return <AcceptRulesStep />;
case QuickStartStep.ACTIVATE_ONLINE:
return <ActivateOnlineStep />;
case QuickStartStep.MIGRATE_DOLPHIN:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const ip = "127.0.0.1";
const port = Ports.DEFAULT;

export const ShareGameplayBlock: React.FC<{ className?: string }> = ({ className }) => {
const playKey = useAccount((store) => store.playKey);
const userData = useAccount((store) => store.userData);
const startTime = useConsole((store) => store.startTime);
const endTime = useConsole((store) => store.endTime);
const slippiStatus = useConsole((store) => store.slippiConnectionStatus);
Expand All @@ -38,7 +38,7 @@ export const ShareGameplayBlock: React.FC<{ className?: string }> = ({ className
ip,
port,
viewerId,
name: playKey ? playKey.connectCode : undefined,
name: userData?.playKey ? userData.playKey.connectCode : undefined,
});
} catch (err) {
log.error(err);
Expand Down
Loading

0 comments on commit 4078a7e

Please sign in to comment.