Chore/convert to typescript (#60)

* Convert to using TypeScript

Next.js v.9 allows converting an existing project to TS by simply renaming the files as TS files

* Fix type errors: Type 'string' is not assignable to type 'number'.

* Add mention about missing typings for javascript-time-ago

* Add GA_INITIALIZED to window

* Fix type error in feed transformers

* Model MembershipInfoForm state and props with TS

* Silence the typescript warning in MembershipInfoForm

* Add threshold to Fader props

* Fix Warning: Each child in a list should have a unique "key" prop.

Sponsors don't have ids – name is probably unique and can be used as a key

* Allow key as prop for SponsorLink

* Add type of the props for SponsorLink
This commit is contained in:
Olavi Haapala
2019-11-07 07:28:02 +02:00
committed by Riku Rouvila
parent bcc6619aee
commit f4474523ad
20 changed files with 125 additions and 26 deletions

View File

@@ -1,10 +1,14 @@
import React from "react";
type Props = {
threshold: number;
};
function clamp(min, max, value) {
return Math.min(Math.max(value, min), max);
}
export default class Fader extends React.Component {
export default class Fader extends React.Component<Props> {
static defaultProps = {
threshold: 100,
};

View File

@@ -8,7 +8,8 @@ import ReactTimeAgo from "react-time-ago";
import JavascriptTimeAgo from "javascript-time-ago";
import timeagoFi from "javascript-time-ago/locale/fi";
JavascriptTimeAgo.locale(timeagoFi);
// TODO: Add type definitions for javascript-time-ago
(JavascriptTimeAgo as any).locale(timeagoFi);
export default class Feed extends React.Component {
state = {
@@ -41,7 +42,7 @@ export default class Feed extends React.Component {
target="_blank"
href={message.imageLink}
rel="noopener noreferrer"
tabIndex="-1"
tabIndex={-1}
>
{image}
</a>

View File

@@ -1,7 +1,14 @@
import React from "react";
import EmailComponent from "./EmailComponent";
import sponsors from "../data/sponsors";
const SponsorLink = ({ href, name }) => (
type Props = {
href: string;
name: string;
key: string;
};
const SponsorLink = ({ href, name }: Props) => (
<a href={href} target="_blank" rel="noopener noreferrer">
<img
src={`/static/images/sponsors/${name.toLowerCase()}.svg`}

View File

@@ -33,7 +33,7 @@ export default class Members extends React.Component {
href="https://github.com/koodiklinikka"
target="_blank"
rel="noopener noreferrer"
tabIndex="-1"
tabIndex={-1}
>
{members}
</a>

View File

@@ -16,7 +16,8 @@ export default {
return items.filter(isVisibleGithubEvent).map(item => {
const template = lodashTemplate(
githubEvent.parse(item).text,
templateSettings
templateSettings,
false
);
const repository = `https://github.com/${item.repo.name}`;

View File

@@ -1,10 +1,28 @@
import pick from "lodash/pick";
import request from "axios";
import React from "react";
import classSet from "classnames";
import api from "../api";
import Loader from "../Loader";
type Props = {
onSignupSuccess: () => void;
};
type State = {
error: boolean;
errors: string[];
fields: {
name: string;
email: string;
handle: string;
address: string;
postcode: string;
city: string;
};
sending: boolean;
pristineFields: string[];
};
const fieldNameTranslations = {
address: { fi: "Osoite" },
city: { fi: "Paikkakunta" },
@@ -20,23 +38,26 @@ function validateEmail(email) {
return mailValidateRe.test(email);
}
const fieldNames = ["name", "email", "handle", "address", "postcode", "city"];
function getUserInfo(state) {
return pick(state, fieldNames);
return state.fields;
}
export default class MembershipInfoForm extends React.Component {
state = {
address: "",
city: "",
email: "",
handle: "",
name: "",
postcode: "",
sending: false,
pristineFields: fieldNames,
};
export default class MembershipInfoForm extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.setState({
fields: {
address: "",
city: "",
email: "",
handle: "",
name: "",
postcode: "",
},
sending: false,
pristineFields: Object.keys(this.state.fields),
});
}
onSubmit = async () => {
this.setState({
@@ -62,7 +83,10 @@ export default class MembershipInfoForm extends React.Component {
}
this.setState({
[e.target.name]: e.target.value,
fields: {
...this.state.fields,
[name]: e.target.value,
},
pristineFields: this.state.pristineFields.filter(
fieldName => fieldName !== name
),
@@ -73,13 +97,13 @@ export default class MembershipInfoForm extends React.Component {
getDataErrors = () => {
const foundErrors = [];
fieldNames.forEach(fieldName => {
Object.keys(this.state.fields).forEach(fieldName => {
if (!this.state[fieldName]) {
foundErrors.push({ field: fieldName, type: "missing" });
}
});
if (this.state.email && !validateEmail(this.state.email)) {
if (this.state.fields.email && !validateEmail(this.state.fields.email)) {
foundErrors.push({ field: "email", type: "invalid" });
}
@@ -119,7 +143,7 @@ export default class MembershipInfoForm extends React.Component {
const fieldsWithErrors = visibleErrors.map(({ field }) => field);
const inputFields = fieldNames.map(fieldName => {
const inputFields = Object.keys(this.state.fields).map(fieldName => {
const inputClasses = classSet({
input: true,
"has-error": fieldsWithErrors.includes(fieldName),