Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Legal updates #85

Merged
merged 18 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions src/Components/Nav/Nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ import AddIcon from "@material-ui/icons/Add";
import BarChartIcon from "@material-ui/icons/BarChart";
import LibraryBooksIcon from "@material-ui/icons/LibraryBooks";
import LocalLibraryIcon from "@material-ui/icons/LocalLibrary";
import SettingsIcon from "@material-ui/icons/Settings";
import PolicyIcon from "@material-ui/icons/Policy";
import SecurityIcon from "@material-ui/icons/Security";
import StyleIcon from "@material-ui/icons/Style";
import VerifiedUserIcon from "@material-ui/icons/VerifiedUser";

import NavButton from "./NavButton.jsx";

import useLinkCreator from "../../hooks/useLinkCreator.js";

import "./Nav.scss";

import { pages } from "../../utils/constants";

const Nav = () => {
const { t } = useTranslation();
const menuStyle = useSelector((state) => state.setting.menuStyle);
Expand All @@ -26,6 +32,15 @@ const Nav = () => {
loading && "nav-loading"
);

const { isValid: isLegalNoticeValid, url: legalNoticeUrl } = useLinkCreator({
path: pages.legalNotice,
electronFix: true,
});
const { isValid: isTermsAndConditionsValid, url: termsAndConditionsUrl } =
useLinkCreator({ path: pages.termsAndConditions });
const { isValid: isPrivacyPolicyValid, url: privacyPolicyUrl } =
useLinkCreator({ path: pages.privacyPolicy });

useEffect(() => {
if (menuStyle === initialStyle) {
return;
Expand Down Expand Up @@ -78,13 +93,33 @@ const Nav = () => {
/>
</ul>

<div>
<NavButton
name={t("nav.settings")}
design={menuStyle}
icon={<SettingsIcon />}
link="/settings"
/>
<div className="nav-legal">
{isLegalNoticeValid && (
<div className="nav-legal-wrapper">
<VerifiedUserIcon />
<a target="_blank" href={legalNoticeUrl} rel="noreferrer">
{t("global.legalNotice")}
</a>
</div>
)}

{isTermsAndConditionsValid && (
<div className="nav-legal-wrapper">
<PolicyIcon />
<a target="_blank" href={termsAndConditionsUrl} rel="noreferrer">
{t("global.termsAndConditions")}
</a>
</div>
)}

{isPrivacyPolicyValid && (
<div className="nav-legal-wrapper">
<SecurityIcon />
<a target="_blank" href={privacyPolicyUrl} rel="noreferrer">
{t("global.privacyPolicy")}
</a>
</div>
)}
</div>
</div>
);
Expand Down
24 changes: 24 additions & 0 deletions src/Components/Nav/Nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,34 @@
justify-content: space-between;
transition: width 0.2s ease-in;

.nav-legal {
color: $color-white;
margin: 10px auto;
font-size: 15px;
opacity: 0;

.nav-legal-wrapper {
display: flex;
align-items: center;
margin: 5px 0;

a {
margin-left: 5px;
color: $color-white;
text-decoration: none;
}
}
}

&.nav-default {
&:hover {
width: 200px;
transition: width 0.2s ease-in;

.nav-legal {
opacity: 100;
transition: all 0.6s ease-in;
}
}
}

Expand Down
49 changes: 49 additions & 0 deletions src/hooks/useLinkCreator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CancelToken } from "axios";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";

import { checkUrlAvailable } from "../utils/api.js";
import { vocascanServer } from "../utils/constants.js";

const useLinkCreator = ({ path, electronFix = false }) => {
const selfHosted = useSelector((state) => state.login.selfHosted);
const serverAddress = useSelector((state) => state.login.serverAddress);
const appLanguage = useSelector((state) => state.setting.language);

const [isValid, setIsValid] = useState(false);
const [url, setUrl] = useState("");

const { BASE_URL: baseURL, ENV: env } = window.VOCASCAN_CONFIG;

// initialize url with the needed url for the servers
useEffect(() => {
if (baseURL || selfHosted) {
if (electronFix && env === "electron") {
setUrl(vocascanServer + `${path}?lang=` + appLanguage);
} else {
setUrl((serverAddress || baseURL) + `${path}?lang=` + appLanguage);
}
} else {
setUrl(vocascanServer + `${path}?lang=` + appLanguage);
}
}, [appLanguage, baseURL, electronFix, env, path, selfHosted, serverAddress]);

// check if url is available
useEffect(() => {
const cancelToken = CancelToken.source();

checkUrlAvailable(url, cancelToken.token)
.then((response) => {
setIsValid(true);
})
.catch((error) => {
setIsValid(false);
});

return () => cancelToken.cancel();
}, [url]);

return { isValid, url };
};

export default useLinkCreator;
9 changes: 7 additions & 2 deletions src/i18n/locales/en/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
"fileImportError": "Error importing your file",
"export": "Export",
"inviteCode": "Invite code",
"create": "Create"
"create": "Create",
"legalNotice": "Legal Notice",
"termsAndConditions": "Terms and Conditions",
"privacyPolicy": "Privacy Policy"
},
"components": {
"groupForm": {
Expand Down Expand Up @@ -306,7 +309,9 @@
"emailInUse": "The email is already used",
"emailNotValid": "Not a valid email address",
"passwordsDontMatch": "The passwords are not the same",
"alreadyHaveAccount": "Already have an account?"
"alreadyHaveAccount": "Already have an account?",
"readPrivacy": "I have read the [Privacy Policy]",
"acceptTerms": "I accept the [Terms and Conditions]"
},
"settings": {
"title": "Settings",
Expand Down
71 changes: 69 additions & 2 deletions src/screens/Register/Register.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import InviteCodeValidIndicator from "../../Components/Indicators/InviteCodeVali
import ServerValidIndicator from "../../Components/Indicators/ServerValidIndicator/ServerValidIndicator.jsx";
import UnauthenticatedLayout from "../../Components/Layout/UnauthenticatedLayout/UnauthenticatedLayout.jsx";

import useLinkCreator from "../../hooks/useLinkCreator.js";
import { setLanguages } from "../../redux/Actions/language.js";
import { setServerUrl, register } from "../../redux/Actions/login.js";
import { register as registerAPI, getLanguages } from "../../utils/api.js";
import {
maxTextfieldLength,
maxUsernameLength,
pages,
} from "../../utils/constants.js";

import "./Register.scss";
Expand All @@ -28,6 +30,13 @@ const Register = ({ image }) => {
const selfHosted = useSelector((state) => state.login.selfHosted);
const languages = useSelector((state) => state.language.languages);

const privacyText = t("screens.register.readPrivacy");
const [privacyTextBefore, privacyTextLink, ...privacyTextAfter] =
privacyText.split(/[[\]]/);
const termsText = t("screens.register.acceptTerms");
const [termsTextBefore, termsTextLink, ...termsTextAfter] =
termsText.split(/[[\]]/);

const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
Expand All @@ -44,9 +53,15 @@ const Register = ({ image }) => {
const [isInviteCodeValid, setIsInviteCodeValid] = useState(false);
const [isUsernameValid, setIsUsernameValid] = useState(false);
const [isEmailValid, setIsEmailValid] = useState(false);

const [readPrivacy, setReadPrivacy] = useState(false);
const [acceptTerms, setAcceptTerms] = useState(false);
const { SHOW_PLANS: showPlans, BASE_URL: baseURL } = window.VOCASCAN_CONFIG;

const { isValid: isPrivacyPolicyValid, url: privacyPolicyUrl } =
useLinkCreator({ path: pages.privacyPolicy });
const { isValid: isTermsAndConditionsValid, url: termsAndConditionsUrl } =
useLinkCreator({ path: pages.termsAndConditions });

const dispatch = useDispatch();

const history = useHistory();
Expand All @@ -55,6 +70,14 @@ const Register = ({ image }) => {
history.push("/login");
}, [history]);

const handleReadPrivacy = useCallback(() => {
setReadPrivacy((readPrivacy) => !readPrivacy);
}, []);

const handleAcceptTerms = useCallback(() => {
setAcceptTerms((acceptTerms) => !acceptTerms);
}, []);

//fetch languages
const fetchLanguages = useCallback(() => {
// when array is empty no languages were stored. Then add them to the store
Expand Down Expand Up @@ -168,7 +191,9 @@ const Register = ({ image }) => {
!isServerValid ||
!isSamePassword ||
!isEmailValid ||
!isUsernameValid
!isUsernameValid ||
(isPrivacyPolicyValid && !readPrivacy) ||
(isTermsAndConditionsValid && !acceptTerms)
)
);
}, [
Expand All @@ -185,6 +210,10 @@ const Register = ({ image }) => {
isSamePassword,
isEmailValid,
isUsernameValid,
readPrivacy,
acceptTerms,
isPrivacyPolicyValid,
isTermsAndConditionsValid,
]);

useEffect(() => {
Expand Down Expand Up @@ -333,6 +362,44 @@ const Register = ({ image }) => {
show={!baseURL}
/>
)}

{isPrivacyPolicyValid && (
<div className="checkbox-wrapper">
<input
type="checkbox"
name="privacy"
onChange={handleReadPrivacy}
/>
<label className="label">
{privacyTextBefore}
<a target="_blank" href={privacyPolicyUrl} rel="noreferrer">
{privacyTextLink}
</a>
{privacyTextAfter}
</label>
</div>
)}

{isTermsAndConditionsValid && (
<div className="checkbox-wrapper">
<input
type="checkbox"
name="terms"
onChange={handleAcceptTerms}
/>
<label className="label">
{termsTextBefore}
<a
target="_blank"
href={termsAndConditionsUrl}
rel="noreferrer"
>
{termsTextLink}
</a>
{termsTextAfter}
</label>
</div>
)}
</div>
<div className="register-form-submit">
<Button
Expand Down
19 changes: 19 additions & 0 deletions src/screens/Register/Register.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,25 @@
font-size: 11px;
color: $color-red;
}

.checkbox-wrapper {
text-align: left;
font-size: 14px;
margin: 3px 0;

.label {
margin-left: 5px;

a {
color: $color-primary;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}
}
}
}

.register-form-submit {
Expand Down
7 changes: 7 additions & 0 deletions src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ export const register = (data, code = null) =>
api.post(`/user/register${code ? `?inviteCode=${code}` : ""}`, data);
export const changePassword = (data) => api.patch("/user/reset-password", data);

// Legal
export const checkUrlAvailable = (url, cancelToken) => {
const checkApi = axios.create();

return checkApi.get(url, { cancelToken });
};

// User
export const getProfile = () => api.get("/user");
export const deleteUser = () => api.delete("/user");
Expand Down
6 changes: 6 additions & 0 deletions src/utils/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
export const vocascanServer = "https://web.vocascan.com";

export const pages = {
legalNotice: "/p/legal-notice",
privacyPolicy: "/p/privacy-policy",
termsAndConditions: "/p/terms-and-conditions",
};

export const maxTranslations = 10;

export const defaultLimit = 100;
Expand Down