more type safety with zod

This commit is contained in:
2023-07-30 12:56:11 +02:00
parent c114a72619
commit 503d42121e
86 changed files with 17389 additions and 644 deletions

View File

@@ -26,7 +26,6 @@
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0",
"flush-promises": "^1.0.2",
"io-ts": "^2.2.20",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
"jest-junit": "^14.0.1",
@@ -140,9 +139,9 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -180,9 +179,9 @@
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -5963,9 +5962,9 @@
}
},
"node_modules/eslint-plugin-jsx-a11y/node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -6372,12 +6371,6 @@
"node": ">= 6"
}
},
"node_modules/fp-ts": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz",
"integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==",
"peer": true
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -6962,14 +6955,6 @@
"node": ">= 0.4"
}
},
"node_modules/io-ts": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.20.tgz",
"integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==",
"peerDependencies": {
"fp-ts": "^2.5.0"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -7387,9 +7372,9 @@
}
},
"node_modules/istanbul-lib-instrument/node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -10820,9 +10805,9 @@
}
},
"node_modules/tough-cookie": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
@@ -11351,9 +11336,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"engines": {
"node": ">=0.10.0"
}
@@ -11540,9 +11525,9 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
}
}
},
@@ -11570,9 +11555,9 @@
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
}
}
},
@@ -15526,9 +15511,9 @@
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
}
}
},
@@ -15810,12 +15795,6 @@
"mime-types": "^2.1.12"
}
},
"fp-ts": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz",
"integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==",
"peer": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -16204,12 +16183,6 @@
"side-channel": "^1.0.4"
}
},
"io-ts": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.20.tgz",
"integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==",
"requires": {}
},
"is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -16479,9 +16452,9 @@
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
}
}
},
@@ -18916,9 +18889,9 @@
}
},
"tough-cookie": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
@@ -19292,9 +19265,9 @@
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
},
"wrap-ansi": {
"version": "7.0.0",

View File

@@ -30,7 +30,6 @@
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0",
"flush-promises": "^1.0.2",
"io-ts": "^2.2.20",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
"jest-junit": "^14.0.1",

View File

@@ -16,7 +16,7 @@ export function AccountComponent(props: IAccountComponentProps) {
return (
<Card className="AuthForm" elevation={2}>
<form
onSubmit={(e: React.FormEvent<any>) => {
onSubmit={(e: React.FormEvent<never>) => {
e.preventDefault();
if (pass.trim()) {
props.changePass(pass);

View File

@@ -1,5 +1,5 @@
import { Position, Toaster } from "@blueprintjs/core";
import { IPhotoReqJSON } from "./shared/types";
import { TPhotoReqJSON } from "./shared/types";
export const AppToaster = Toaster.create({
className: "recipe-toaster",
@@ -43,7 +43,7 @@ export function showPhotoCreateFailToast(f: File, e: string): void {
}
export function showPhotoUploadJSONFailToast(
p: IPhotoReqJSON | number,
p: TPhotoReqJSON | number,
e: string,
): void {
const photoMsg = typeof p === "number" ? p : p.hash;

View File

@@ -23,7 +23,9 @@ export class AuthScreenComponent extends React.PureComponent<IAuthScreenProps> {
}
public render() {
const { location } = this.props.history;
const { from } = (this.props.location.state as any) || { from: "/" };
const { from } = (this.props.location.state as { from: string }) || {
from: "/",
};
const { loggedIn } = this.props;
return loggedIn ? (
<Redirect to={from} />
@@ -46,7 +48,7 @@ export class AuthScreenComponent extends React.PureComponent<IAuthScreenProps> {
transform: "translate3d(400px,0,0)",
}}
>
{(_location: any) => (style: any) => (
{(_location) => (style) => (
<animated.div style={style}>
<Switch location={_location}>
<Route path="/login" component={Login} />

View File

@@ -36,7 +36,7 @@ export class LoginComponent extends React.PureComponent<
this.props.history.push("/signup");
}
public submit(e: React.FormEvent<any>) {
public submit<T extends React.FormEvent>(e: T) {
e.preventDefault();
const { username, password } = this.state;
if (!this.props.inProgress) {

View File

@@ -37,7 +37,7 @@ export class SignupComponent extends React.PureComponent<
this.props.history.push("/login");
}
public submit(e: React.FormEvent<any>) {
public submit<T extends React.FormEvent>(e: T) {
e.preventDefault();
const { username, password, email } = this.state;
if (!this.props.inProgress) {

View File

@@ -14,7 +14,7 @@ import { connect } from "react-redux";
import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
import { animated, config, Transition } from "react-spring/renderprops";
import { Dispatch } from "redux";
import { IUserJSON } from "../shared/types";
import { TUserJSON } from "../shared/types";
import { Account } from "../Account/Account";
import { Overview } from "../Photos/Overview";
import { toggleDarkMode } from "../redux/localSettings/actions";
@@ -24,7 +24,7 @@ import { PhotoRoute } from "../Photos/PhotoRoute";
import { UploadStatus } from "./UploadStatus";
export interface IHomeProps extends RouteComponentProps {
user: IUserJSON | null;
user: TUserJSON | null;
darkMode: boolean;
@@ -90,7 +90,7 @@ export class HomeComponent extends React.PureComponent<IHomeProps> {
transform: "translate3d(400px,0,0)",
}}
>
{(_location: any) => (style: any) => (
{(_location) => (style) => (
<animated.div
style={style}
className="viewComponent"

View File

@@ -9,7 +9,7 @@ import {
photosDeleteStart,
photosLoadStart,
} from "../redux/photos/actions";
import { IPhotoReqJSON } from "~/src/shared/types";
import { TPhotoReqJSON } from "~/src/shared/types";
import { PhotoCard } from "./PhotoCard";
import {
Alignment,
@@ -26,7 +26,7 @@ import { Photo } from "./Photo";
import { showDeletionToast } from "~/src/AppToaster";
export interface IOverviewComponentProps {
photos: IPhotoReqJSON[];
photos: TPhotoReqJSON[];
triedLoading: boolean;
allPhotosLoaded: boolean;
overviewFetching: boolean;
@@ -35,8 +35,8 @@ export interface IOverviewComponentProps {
darkMode: boolean;
fetchPhotos: () => void;
startDeletePhotos: (photos: IPhotoReqJSON[]) => void;
cancelDelete: (photos: IPhotoReqJSON[]) => void;
startDeletePhotos: (photos: TPhotoReqJSON[]) => void;
cancelDelete: (photos: TPhotoReqJSON[]) => void;
}
const PhotoCardM = React.memo(PhotoCard);
@@ -80,7 +80,7 @@ export const OverviewComponent: React.FunctionComponent<
(
acc: Record<
string,
Record<string, Record<string, IPhotoReqJSON[]>>
Record<string, Record<string, TPhotoReqJSON[]>>
>,
photo,
) => {
@@ -108,7 +108,7 @@ export const OverviewComponent: React.FunctionComponent<
const els = Object.keys(dates[year]).reduce(
(accMonths: JSX.Element[], month): JSX.Element[] => {
const photos = Object.values(dates[year][month]).reduce(
(accDays: IPhotoReqJSON[], day) => {
(accDays: TPhotoReqJSON[], day) => {
return [...day, ...accDays];
},
[],
@@ -264,9 +264,9 @@ function mapStateToProps(state: IAppState) {
function mapDispatchToProps(dispatch: Dispatch) {
return {
fetchPhotos: () => dispatch(photosLoadStart()),
startDeletePhotos: (photos: IPhotoReqJSON[]) =>
startDeletePhotos: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteStart(photos)),
cancelDelete: (photos: IPhotoReqJSON[]) =>
cancelDelete: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteCancel(photos)),
};
}

View File

@@ -2,24 +2,31 @@ import "./Photo.scss";
import * as React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { IPhotoReqJSON } from "~/src/shared/types";
import { TPhotoReqJSON } from "~/src/shared/types";
import { LoadingStub } from "../LoadingStub";
import { getPhotoImgPath, getPhotoThumbPath } from "../redux/api/photos";
import { photoLoadStart } from "../redux/photos/actions";
import { IPhotoState } from "../redux/photos/reducer";
import { TPhotoState } from "../redux/photos/reducer";
import { IAppState } from "../redux/reducers";
import { LargeSize, PreviewSize } from "./helper";
export interface IPhotoComponentProps {
id: number;
photo: IPhotoReqJSON | undefined;
photoState: IPhotoState | undefined;
type StateProps = {
photo: TPhotoReqJSON | undefined;
photoState: TPhotoState | undefined;
};
type DispatchProps = {
fetchPhoto: (id: number) => void;
close: () => void;
}
};
export const PhotoComponent: React.FunctionComponent<IPhotoComponentProps> = (
type OwnProps = {
id: number;
close: () => void;
};
export type TPhotoComponentProps = StateProps & DispatchProps & OwnProps;
export const PhotoComponent: React.FunctionComponent<TPhotoComponentProps> = (
props,
) => {
const [smallPreviewFetching, setSmallPreviewFetching] =
@@ -114,7 +121,7 @@ export const PhotoComponent: React.FunctionComponent<IPhotoComponentProps> = (
);
};
function mapStateToProps(state: IAppState, props: IPhotoComponentProps) {
function mapStateToProps(state: IAppState, props: OwnProps) {
const { id } = props;
return {
photo: state.photos?.photos?.find((p) => p.id === id),
@@ -126,8 +133,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
return { fetchPhoto: (id: number) => dispatch(photoLoadStart(id)) };
}
// Because https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16990
export const Photo = connect(
export const Photo = connect<StateProps, DispatchProps, OwnProps, IAppState>(
mapStateToProps,
mapDispatchToProps,
)(PhotoComponent) as any;
)(PhotoComponent);

View File

@@ -8,7 +8,7 @@ import {
Spinner,
} from "@blueprintjs/core";
import * as React from "react";
import { IPhotoReqJSON } from "~/src/shared/types";
import { TPhotoReqJSON } from "~/src/shared/types";
import { getPhotoThumbPath } from "../redux/api/photos";
import { showDeletionToast } from "../AppToaster";
import { Dispatch } from "redux";
@@ -17,30 +17,30 @@ import { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router";
import { PreviewSize } from "./helper";
export interface IPhotoCardComponentProps extends RouteComponentProps {
photo: IPhotoReqJSON;
export interface TPhotoCardComponentProps extends RouteComponentProps {
photo: TPhotoReqJSON;
selected: boolean;
id: string;
deletePhoto: (photos: IPhotoReqJSON[]) => void;
cancelDelete: (photos: IPhotoReqJSON[]) => void;
deletePhoto: (photos: TPhotoReqJSON[]) => void;
cancelDelete: (photos: TPhotoReqJSON[]) => void;
onClick: (e: React.MouseEvent<HTMLElement>) => void;
}
export interface IPhotoCardComponentState {
export interface TPhotoCardComponentState {
loaded: boolean;
}
const defaultPhotoCardState: IPhotoCardComponentState = {
const defaultPhotoCardState: TPhotoCardComponentState = {
loaded: false,
};
@ContextMenuTarget
export class PhotoCardComponent extends React.PureComponent<
IPhotoCardComponentProps,
IPhotoCardComponentState
TPhotoCardComponentProps,
TPhotoCardComponentState
> {
constructor(props: IPhotoCardComponentProps) {
constructor(props: TPhotoCardComponentProps) {
super(props);
this.handleDelete = this.handleDelete.bind(this);
@@ -121,9 +121,9 @@ export class PhotoCardComponent extends React.PureComponent<
function mapDispatchToProps(dispatch: Dispatch) {
return {
deletePhoto: (photos: IPhotoReqJSON[]) =>
deletePhoto: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteStart(photos)),
cancelDelete: (photos: IPhotoReqJSON[]) =>
cancelDelete: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteCancel(photos)),
};
}

View File

@@ -6,12 +6,12 @@ function getId(props: RouteComponentProps) {
return parseInt((props.match?.params as { id: string }).id);
}
export const PhotoRouteComponent: React.FunctionComponent<RouteComponentProps> = (
props: RouteComponentProps,
) => {
export const PhotoRouteComponent: React.FunctionComponent<
RouteComponentProps
> = (props: RouteComponentProps) => {
const id = getId(props);
return <Photo id={id} />;
return <Photo id={id} close={() => {}} />;
};
export const PhotoRoute = withRouter(PhotoRouteComponent);

View File

@@ -1,11 +1,16 @@
import { IUserLoginRespBody, IUserSignupRespBody } from "~/src/shared/types";
import {
TUserLoginRespBody,
TUserSignupRespBody,
UserLoginRespBody,
UserSignupRespBody,
} from "~/src/shared/types";
import { fetchJSON } from "../utils";
export async function login(
username: string,
password: string,
): Promise<IUserLoginRespBody> {
return fetchJSON("/users/login", "POST", {
): Promise<TUserLoginRespBody> {
return fetchJSON("/users/login", "POST", UserLoginRespBody, {
username,
password,
});
@@ -15,8 +20,8 @@ export async function signup(
username: string,
password: string,
email: string,
): Promise<IUserSignupRespBody> {
return fetchJSON("/users/signup", "POST", {
): Promise<TUserSignupRespBody> {
return fetchJSON("/users/signup", "POST", UserSignupRespBody, {
username,
password,
email,

View File

@@ -1,19 +1,26 @@
import { IPhotoReqJSON } from "~/src/shared/types";
import {
IPhotosByIDGetRespBody,
IPhotosDeleteRespBody,
IPhotosListRespBody,
IPhotosNewRespBody,
IPhotosUploadRespBody,
PhotosByIDGetRespBody,
PhotosDeleteRespBody,
PhotosListRespBody,
PhotosNewRespBody,
PhotosUploadRespBody,
TPhotoReqJSON,
} from "~/src/shared/types";
import {
TPhotosByIDGetRespBody,
TPhotosDeleteRespBody,
TPhotosListRespBody,
TPhotosNewRespBody,
TPhotosUploadRespBody,
} from "~/src/shared/types";
import { apiRoot } from "~src/env";
import { fetchJSONAuth } from "./utils";
export function getPhotoImgPath(photo: IPhotoReqJSON): string {
export function getPhotoImgPath(photo: TPhotoReqJSON): string {
return `${apiRoot}/photos/showByID/${photo.id}/${photo.accessToken}`;
}
export function getPhotoThumbPath(photo: IPhotoReqJSON, size: number): string {
export function getPhotoThumbPath(photo: TPhotoReqJSON, size: number): string {
return `${apiRoot}/photos/showByID/${photo.id}/${
photo.accessToken
}?size=${size.toString()}`;
@@ -22,35 +29,50 @@ export function getPhotoThumbPath(photo: IPhotoReqJSON, size: number): string {
export async function fetchPhotosList(
skip: number,
num: number,
): Promise<IPhotosListRespBody> {
): Promise<TPhotosListRespBody> {
const params = new URLSearchParams({
skip: skip.toString(),
num: num.toString(),
});
return fetchJSONAuth(`/photos/list?${params.toString()}`, "GET");
return fetchJSONAuth(
`/photos/list?${params.toString()}`,
"GET",
PhotosListRespBody,
);
}
export async function fetchPhoto(id: number): Promise<IPhotosByIDGetRespBody> {
return fetchJSONAuth(`/photos/byID/${id}`, "GET");
export async function fetchPhoto(id: number): Promise<TPhotosByIDGetRespBody> {
return fetchJSONAuth(`/photos/byID/${id}`, "GET", PhotosByIDGetRespBody);
}
export async function createPhoto(
hash: string,
size: string,
format: string,
): Promise<IPhotosNewRespBody> {
return fetchJSONAuth("/photos/new", "POST", { hash, size, format });
): Promise<TPhotosNewRespBody> {
return fetchJSONAuth("/photos/new", "POST", PhotosNewRespBody, {
hash,
size,
format,
});
}
export async function uploadPhoto(
file: File,
id: number,
): Promise<IPhotosUploadRespBody> {
return fetchJSONAuth(`/photos/upload/${id}`, "POST", file);
): Promise<TPhotosUploadRespBody> {
return fetchJSONAuth(
`/photos/upload/${id}`,
"POST",
PhotosUploadRespBody,
file,
);
}
export async function deletePhotos(
photos: IPhotoReqJSON[],
): Promise<IPhotosDeleteRespBody> {
return fetchJSONAuth(`/photos/delete`, "POST", { photos });
photos: TPhotoReqJSON[],
): Promise<TPhotosDeleteRespBody> {
return fetchJSONAuth(`/photos/delete`, "POST", PhotosDeleteRespBody, {
photos,
});
}

View File

@@ -1,17 +1,19 @@
import { fetchJSONAuth } from "../utils";
import { IUserEditRespBody, IUserGetRespBody } from "~/src/shared/types";
import {
TUserEditRespBody,
TUserGetRespBody,
UserEditRespBody,
UserGetRespBody,
} from "~/src/shared/types";
export async function fetchUser(): Promise<IUserGetRespBody> {
return fetchJSONAuth(
"/users/user",
"GET",
) as unknown as Promise<IUserGetRespBody>;
export async function fetchUser(): Promise<TUserGetRespBody> {
return fetchJSONAuth("/users/user", "GET", UserGetRespBody);
}
export async function changeUserPassword(
newPassword: string,
): Promise<IUserEditRespBody> {
return fetchJSONAuth("/users/edit", "POST", {
): Promise<TUserEditRespBody> {
return fetchJSONAuth("/users/edit", "POST", UserEditRespBody, {
password: newPassword,
});
}

View File

@@ -1,5 +1,4 @@
import { apiRoot } from "~src/env";
import { IAPIResponse } from "~/src/shared/types";
let token: string | null;
@@ -15,49 +14,44 @@ export function deleteToken(): void {
token = null;
}
export async function fetchJSON<T>(
export async function fetchJSON<T, P extends { parse: (string) => T }>(
path: string,
method: string,
parser: P,
body?: string | Record<string, unknown> | File,
headers?: Record<string, string>,
): Promise<IAPIResponse<T>> {
if (typeof body === "object" && !(body instanceof File)) {
body = JSON.stringify(body);
headers = {
...headers,
"Content-Type": "application/json",
};
}
// TODO: io-ts or something like that
if (body instanceof File) {
const formData = new FormData();
formData.append("photo", body);
const response = await fetch(apiRoot + path, {
method,
headers,
body: formData,
});
const json = (await response.json()) as Record<string, unknown>;
return json as unknown as IAPIResponse<T>;
}
): Promise<T> {
const reqBody = () =>
body instanceof File
? (() => {
const fd = new FormData();
fd.append("photo", body);
return fd;
})()
: JSON.stringify(body);
const reqHeaders = () =>
body instanceof File
? headers
: { ...headers, "Content-Type": "application/json" };
const response = await fetch(apiRoot + path, {
method,
body,
headers,
headers: reqHeaders(),
body: reqBody(),
});
const json = (await response.json()) as Record<string, unknown>;
return json as unknown as IAPIResponse<T>;
return parser.parse(await response.json());
}
export async function fetchJSONAuth<T>(
export async function fetchJSONAuth<T, P extends { parse: (string) => T }>(
path: string,
method: string,
parser: P,
body?: string | Record<string, unknown> | File,
headers?: Record<string, unknown>,
): Promise<IAPIResponse<T>> {
): Promise<T> {
if (token) {
return fetchJSON(path, method, body, {
return fetchJSON(path, method, parser, body, {
...headers,
Authorization: `Bearer ${token}`,
});

View File

@@ -1,5 +1,5 @@
import { Action } from "redux";
import { IUserAuthJSON } from "~/src/shared/types";
import { TUserAuthJSON } from "~/src/shared/types";
export enum AuthTypes {
AUTH_START = "AUTH_START",
@@ -28,7 +28,7 @@ export interface ISignupStartAction extends Action {
export interface IAuthSuccessAction extends Action {
type: AuthTypes.AUTH_SUCCESS;
payload: IUserAuthJSON;
payload: TUserAuthJSON;
}
export interface IAuthFailureAction extends Action {
@@ -64,7 +64,7 @@ export function signupStart(
};
}
export function authSuccess(user: IUserAuthJSON): IAuthSuccessAction {
export function authSuccess(user: TUserAuthJSON): IAuthSuccessAction {
return { type: AuthTypes.AUTH_SUCCESS, payload: user };
}

View File

@@ -1,5 +1,5 @@
import { Action } from "redux";
import { IPhotoReqJSON } from "~src/shared/types";
import { TPhotoReqJSON } from "~src/shared/types";
import {
showPhotoCreateFailToast,
showPhotoUploadFileFailToast,
@@ -29,242 +29,242 @@ export enum PhotoTypes {
PHOTOS_DELETE_CANCEL = "PHOTOS_DELETE_CANCEL",
}
export interface IPhotosLoadStartAction extends Action {
export interface TPhotosLoadStartAction extends Action {
type: PhotoTypes.PHOTOS_LOAD_START;
}
export interface IPhotosLoadSuccessAction extends Action {
export interface TPhotosLoadSuccessAction extends Action {
type: PhotoTypes.PHOTOS_LOAD_SUCCESS;
photos: IPhotoReqJSON[];
photos: TPhotoReqJSON[];
}
export interface IPhotosLoadFailAction extends Action {
export interface TPhotosLoadFailAction extends Action {
type: PhotoTypes.PHOTOS_LOAD_FAIL;
error: string;
}
export interface IPhotoLoadStartAction extends Action {
export interface TPhotoLoadStartAction extends Action {
type: PhotoTypes.PHOTO_LOAD_START;
id: number;
}
export interface IPhotoLoadSuccessAction extends Action {
export interface TPhotoLoadSuccessAction extends Action {
type: PhotoTypes.PHOTO_LOAD_SUCCESS;
photo: IPhotoReqJSON;
photo: TPhotoReqJSON;
}
export interface IPhotoLoadFailAction extends Action {
export interface TPhotoLoadFailAction extends Action {
type: PhotoTypes.PHOTO_LOAD_FAIL;
id: number;
error: string;
}
export interface IPhotosUploadStartAction extends Action {
export interface TPhotosUploadStartAction extends Action {
type: PhotoTypes.PHOTOS_UPLOAD_START;
files: FileList;
}
export interface IPhotoCreateQueue extends Action {
export interface TPhotoCreateQueue extends Action {
type: PhotoTypes.PHOTO_CREATE_QUEUE;
file: File;
}
export interface IPhotoUploadQueue extends Action {
export interface TPhotoUploadQueue extends Action {
type: PhotoTypes.PHOTO_UPLOAD_QUEUE;
file: File;
id: number;
}
export interface IPhotoCreateStart extends Action {
export interface TPhotoCreateStart extends Action {
type: PhotoTypes.PHOTO_CREATE_START;
file: File;
}
export interface IPhotoUploadStart extends Action {
export interface TPhotoUploadStart extends Action {
type: PhotoTypes.PHOTO_UPLOAD_START;
file: File;
id: number;
}
export interface IPhotoUploadSuccessAction extends Action {
export interface TPhotoUploadSuccessAction extends Action {
type: PhotoTypes.PHOTO_UPLOAD_SUCCESS;
photo: IPhotoReqJSON;
photo: TPhotoReqJSON;
}
export interface IPhotoUploadFailAction extends Action {
export interface TPhotoUploadFailAction extends Action {
type: PhotoTypes.PHOTO_UPLOAD_FAIL;
photo: IPhotoReqJSON | number;
photo: TPhotoReqJSON | number;
error: string;
}
export interface IPhotoCreateSuccessAction extends Action {
export interface TPhotoCreateSuccessAction extends Action {
type: PhotoTypes.PHOTO_CREATE_SUCCESS;
photo: IPhotoReqJSON;
photo: TPhotoReqJSON;
file: File;
}
export interface IPhotoCreateFailAction extends Action {
export interface TPhotoCreateFailAction extends Action {
type: PhotoTypes.PHOTO_CREATE_FAIL;
file: File;
error: string;
}
export interface IPhotosDeleteStartAction extends Action {
export interface TPhotosDeleteStartAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_START;
photos: IPhotoReqJSON[];
photos: TPhotoReqJSON[];
}
export interface IPhotosDeleteSuccessAction extends Action {
export interface TPhotosDeleteSuccessAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_SUCCESS;
photos: IPhotoReqJSON[];
photos: TPhotoReqJSON[];
}
export interface IPhotosDeleteFailAction extends Action {
export interface TPhotosDeleteFailAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_FAIL;
photos: IPhotoReqJSON[];
photos: TPhotoReqJSON[];
error?: string;
}
export interface IPhotosDeleteCancelAction extends Action {
export interface TPhotosDeleteCancelAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_CANCEL;
photos: IPhotoReqJSON[];
photos: TPhotoReqJSON[];
}
export interface IPhotosStartFetchingSpinner extends Action {
export interface TPhotosStartFetchingSpinner extends Action {
type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER;
}
export function photoCreateQueue(file: File): IPhotoCreateQueue {
export function photoCreateQueue(file: File): TPhotoCreateQueue {
return { type: PhotoTypes.PHOTO_CREATE_QUEUE, file };
}
export function photoUploadQueue(file: File, id: number): IPhotoUploadQueue {
export function photoUploadQueue(file: File, id: number): TPhotoUploadQueue {
return { type: PhotoTypes.PHOTO_UPLOAD_QUEUE, file, id };
}
export function photoCreateStart(file: File): IPhotoCreateStart {
export function photoCreateStart(file: File): TPhotoCreateStart {
return { type: PhotoTypes.PHOTO_CREATE_START, file };
}
export function photoUploadStart(file: File, id: number): IPhotoUploadStart {
export function photoUploadStart(file: File, id: number): TPhotoUploadStart {
return { type: PhotoTypes.PHOTO_UPLOAD_START, file, id };
}
export function photosLoadStart(): IPhotosLoadStartAction {
export function photosLoadStart(): TPhotosLoadStartAction {
return { type: PhotoTypes.PHOTOS_LOAD_START };
}
export function photoLoadStart(id: number): IPhotoLoadStartAction {
export function photoLoadStart(id: number): TPhotoLoadStartAction {
return { type: PhotoTypes.PHOTO_LOAD_START, id };
}
export function photosUploadStart(files: FileList): IPhotosUploadStartAction {
export function photosUploadStart(files: FileList): TPhotosUploadStartAction {
return { type: PhotoTypes.PHOTOS_UPLOAD_START, files };
}
export function photoUploadSuccess(
photo: IPhotoReqJSON,
): IPhotoUploadSuccessAction {
photo: TPhotoReqJSON,
): TPhotoUploadSuccessAction {
return { type: PhotoTypes.PHOTO_UPLOAD_SUCCESS, photo };
}
export function photoUploadFail(
photo: IPhotoReqJSON | number,
photo: TPhotoReqJSON | number,
error: string,
): IPhotoUploadFailAction {
): TPhotoUploadFailAction {
showPhotoUploadJSONFailToast(photo, error);
return { type: PhotoTypes.PHOTO_UPLOAD_FAIL, photo, error };
}
export function photoUploadFailWithFile(
photo: IPhotoReqJSON | number,
photo: TPhotoReqJSON | number,
file: File,
error: string,
): IPhotoUploadFailAction {
): TPhotoUploadFailAction {
showPhotoUploadFileFailToast(file, error);
return { type: PhotoTypes.PHOTO_UPLOAD_FAIL, photo, error };
}
export function photoCreateSuccess(
photo: IPhotoReqJSON,
photo: TPhotoReqJSON,
file: File,
): IPhotoCreateSuccessAction {
): TPhotoCreateSuccessAction {
return { type: PhotoTypes.PHOTO_CREATE_SUCCESS, photo, file };
}
export function photoCreateFail(
file: File,
error: string,
): IPhotoCreateFailAction {
): TPhotoCreateFailAction {
showPhotoCreateFailToast(file, error);
return { type: PhotoTypes.PHOTO_CREATE_FAIL, file, error };
}
export function photosLoadSuccess(
photos: IPhotoReqJSON[],
): IPhotosLoadSuccessAction {
photos: TPhotoReqJSON[],
): TPhotosLoadSuccessAction {
return { type: PhotoTypes.PHOTOS_LOAD_SUCCESS, photos };
}
export function photosLoadFail(error: string): IPhotosLoadFailAction {
export function photosLoadFail(error: string): TPhotosLoadFailAction {
return { type: PhotoTypes.PHOTOS_LOAD_FAIL, error };
}
export function photoLoadSuccess(
photo: IPhotoReqJSON,
): IPhotoLoadSuccessAction {
photo: TPhotoReqJSON,
): TPhotoLoadSuccessAction {
return { type: PhotoTypes.PHOTO_LOAD_SUCCESS, photo };
}
export function photoLoadFail(id: number, error: string): IPhotoLoadFailAction {
export function photoLoadFail(id: number, error: string): TPhotoLoadFailAction {
return { type: PhotoTypes.PHOTO_LOAD_FAIL, id, error };
}
export function photosDeleteStart(
photos: IPhotoReqJSON[],
): IPhotosDeleteStartAction {
photos: TPhotoReqJSON[],
): TPhotosDeleteStartAction {
return { type: PhotoTypes.PHOTOS_DELETE_START, photos };
}
export function photosDeleteSuccess(
photos: IPhotoReqJSON[],
): IPhotosDeleteSuccessAction {
photos: TPhotoReqJSON[],
): TPhotosDeleteSuccessAction {
return { type: PhotoTypes.PHOTOS_DELETE_SUCCESS, photos };
}
export function photosDeleteFail(
photos: IPhotoReqJSON[],
photos: TPhotoReqJSON[],
error?: string,
): IPhotosDeleteFailAction {
): TPhotosDeleteFailAction {
return { type: PhotoTypes.PHOTOS_DELETE_FAIL, photos, error };
}
export function photosDeleteCancel(
photos: IPhotoReqJSON[],
): IPhotosDeleteCancelAction {
photos: TPhotoReqJSON[],
): TPhotosDeleteCancelAction {
return { type: PhotoTypes.PHOTOS_DELETE_CANCEL, photos };
}
export function photosStartFetchingSpinner(): IPhotosStartFetchingSpinner {
export function photosStartFetchingSpinner(): TPhotosStartFetchingSpinner {
return { type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER };
}
export type PhotoAction =
| IPhotosLoadStartAction
| IPhotosLoadFailAction
| IPhotosLoadSuccessAction
| IPhotosStartFetchingSpinner
| IPhotosUploadStartAction
| IPhotoCreateFailAction
| IPhotoCreateSuccessAction
| IPhotoUploadFailAction
| IPhotoUploadSuccessAction
| IPhotosDeleteFailAction
| IPhotosDeleteStartAction
| IPhotosDeleteSuccessAction
| IPhotosDeleteCancelAction
| IPhotoLoadFailAction
| IPhotoLoadStartAction
| IPhotoLoadSuccessAction
| IPhotoUploadQueue
| IPhotoCreateQueue
| IPhotoCreateStart
| IPhotoUploadStart;
| TPhotosLoadStartAction
| TPhotosLoadFailAction
| TPhotosLoadSuccessAction
| TPhotosStartFetchingSpinner
| TPhotosUploadStartAction
| TPhotoCreateFailAction
| TPhotoCreateSuccessAction
| TPhotoUploadFailAction
| TPhotoUploadSuccessAction
| TPhotosDeleteFailAction
| TPhotosDeleteStartAction
| TPhotosDeleteSuccessAction
| TPhotosDeleteCancelAction
| TPhotoLoadFailAction
| TPhotoLoadStartAction
| TPhotoLoadSuccessAction
| TPhotoUploadQueue
| TPhotoCreateQueue
| TPhotoCreateStart
| TPhotoUploadStart;

View File

@@ -1,17 +1,17 @@
import { Reducer } from "redux";
import { IPhotoReqJSON } from "~/src/shared/types";
import { TPhotoReqJSON } from "~/src/shared/types";
import { UserAction, UserTypes } from "~src/redux/user/actions";
import { PhotoAction, PhotoTypes } from "./actions";
export interface IPhotoState {
export interface TPhotoState {
fetching: boolean;
fetchingError: string | null;
}
export interface IPhotosState {
photos: IPhotoReqJSON[];
export interface TPhotosState {
photos: TPhotoReqJSON[];
photoStates: Record<number, IPhotoState>;
photoStates: Record<number, TPhotoState>;
overviewFetching: boolean;
allPhotosLoaded: boolean;
@@ -24,10 +24,10 @@ export interface IPhotosState {
photoUploadQueue: Record<number, File>;
photosUploading: number;
deleteCache: Record<number, IPhotoReqJSON>;
deleteCache: Record<number, TPhotoReqJSON>;
}
const defaultPhotosState: IPhotosState = {
const defaultPhotosState: TPhotosState = {
photos: [],
allPhotosLoaded: false,
overviewFetching: false,
@@ -45,12 +45,12 @@ const defaultPhotosState: IPhotosState = {
deleteCache: {},
};
export function sortPhotos(photos: IPhotoReqJSON[]): IPhotoReqJSON[] {
export function sortPhotos(photos: TPhotoReqJSON[]): TPhotoReqJSON[] {
return [...photos].sort((a, b) => b.shotAt - a.shotAt);
}
export const photosReducer: Reducer<IPhotosState, PhotoAction> = (
state: IPhotosState = defaultPhotosState,
export const photosReducer: Reducer<TPhotosState, PhotoAction> = (
state: TPhotosState = defaultPhotosState,
action: PhotoAction | UserAction,
) => {
switch (action.type) {
@@ -230,7 +230,7 @@ export const photosReducer: Reducer<IPhotosState, PhotoAction> = (
case PhotoTypes.PHOTOS_DELETE_FAIL:
case PhotoTypes.PHOTOS_DELETE_CANCEL: {
const delCache = { ...state.deleteCache };
let photos: IPhotoReqJSON[] = [...state.photos];
let photos: TPhotoReqJSON[] = [...state.photos];
for (const photo of action.photos) {
if (delCache[photo.id]) {
photos = sortPhotos([...photos, delCache[photo.id]]);

View File

@@ -18,9 +18,9 @@ import {
uploadPhoto,
} from "~src/redux/api/photos";
import {
IPhotosDeleteStartAction,
IPhotoLoadStartAction,
IPhotosUploadStartAction,
TPhotosDeleteStartAction,
TPhotoLoadStartAction,
TPhotosUploadStartAction,
photoCreateFail,
photoCreateQueue,
photoCreateStart,
@@ -38,7 +38,7 @@ import {
photoUploadStart,
photoUploadSuccess,
} from "./actions";
import { IPhotosNewRespBody, IPhotosListPagination } from "~src/shared/types";
import { TPhotosNewRespBody, PhotosListPagination } from "~src/shared/types";
// Thanks, https://dev.to/qortex/compute-md5-checksum-for-a-file-in-typescript-59a4
function computeChecksumMd5(file: File): Promise<string> {
@@ -112,7 +112,7 @@ function* photosLoad() {
const skip = state.photos.photos ? state.photos.photos.length : 0;
const { response, timeout } = yield race({
response: call(fetchPhotosList, skip, IPhotosListPagination),
response: call(fetchPhotosList, skip, PhotosListPagination),
timeout: delay(10000),
});
@@ -133,7 +133,7 @@ function* photosLoad() {
}
}
function* photoLoad(action: IPhotoLoadStartAction) {
function* photoLoad(action: TPhotoLoadStartAction) {
try {
//const spinner = yield fork(startSpinner);
@@ -185,7 +185,7 @@ function* photoCreate() {
return;
}
if (response.data || response.error === "Photo already exists") {
const photo = (response as IPhotosNewRespBody).data;
const photo = (response as TPhotosNewRespBody).data;
yield put(photoCreateSuccess(photo, f));
yield put(photoUploadQueue(f, photo.id));
} else {
@@ -233,14 +233,14 @@ function* photoUpload() {
}
}
function* photosUpload(action: IPhotosUploadStartAction) {
function* photosUpload(action: TPhotosUploadStartAction) {
const files = Array.from(action.files);
for (const file of files) {
yield put(photoCreateQueue(file));
}
}
function* photosDelete(action: IPhotosDeleteStartAction) {
function* photosDelete(action: TPhotosDeleteStartAction) {
try {
const { cancelled } = yield race({
timeout: delay(3000),

View File

@@ -7,14 +7,14 @@ import {
ILocalSettingsState,
localSettingsReducer,
} from "./localSettings/reducer";
import { IPhotosState, photosReducer } from "./photos/reducer";
import { IUserState, userReducer } from "./user/reducer";
import { TPhotosState, photosReducer } from "./photos/reducer";
import { TUserState, userReducer } from "./user/reducer";
export interface IAppState {
auth: IAuthState & PersistPartial;
user: IUserState;
user: TUserState;
localSettings: ILocalSettingsState & PersistPartial;
photos: IPhotosState;
photos: TPhotosState;
}
const authPersistConfig = {

View File

@@ -1,5 +1,5 @@
import { Action } from "redux";
import { IUserAuthJSON } from "~src/shared/types";
import { TUserAuthJSON } from "~src/shared/types";
import {
showPasswordNotSavedToast,
showPasswordSavedToast,
@@ -15,20 +15,20 @@ export enum UserTypes {
USER_PASS_CHANGE_FAIL = "USER_PASS_CHANGE_FAIL",
}
export interface IUserGetAction extends Action {
export interface TUserGetAction extends Action {
type: UserTypes.USER_GET;
}
export interface IUserLogoutAction extends Action {
export interface TUserLogoutAction extends Action {
type: UserTypes.USER_LOGOUT;
}
export interface IUserGetSuccessAction extends Action {
export interface TUserGetSuccessAction extends Action {
type: UserTypes.USER_GET_SUCCESS;
payload: IUserAuthJSON;
payload: TUserAuthJSON;
}
export interface IUserGetFailAction extends Action {
export interface TUserGetFailAction extends Action {
type: UserTypes.USER_GET_FAIL;
payload: {
error: string;
@@ -36,17 +36,17 @@ export interface IUserGetFailAction extends Action {
};
}
export interface IUserPassChangeAction extends Action {
export interface TUserPassChangeAction extends Action {
type: UserTypes.USER_PASS_CHANGE;
password: string;
}
export interface IUserPassChangeSuccessAction extends Action {
export interface TUserPassChangeSuccessAction extends Action {
type: UserTypes.USER_PASS_CHANGE_SUCCESS;
payload: IUserAuthJSON;
payload: TUserAuthJSON;
}
export interface IUserPassChangeFailAction extends Action {
export interface TUserPassChangeFailAction extends Action {
type: UserTypes.USER_PASS_CHANGE_FAIL;
payload: {
error: string;
@@ -54,32 +54,32 @@ export interface IUserPassChangeFailAction extends Action {
};
}
export function getUser(): IUserGetAction {
export function getUser(): TUserGetAction {
return { type: UserTypes.USER_GET };
}
export function logoutUser(): IUserLogoutAction {
export function logoutUser(): TUserLogoutAction {
return { type: UserTypes.USER_LOGOUT };
}
export function getUserSuccess(user: IUserAuthJSON): IUserGetSuccessAction {
export function getUserSuccess(user: TUserAuthJSON): TUserGetSuccessAction {
return { type: UserTypes.USER_GET_SUCCESS, payload: user };
}
export function getUserFail(
error: string,
logout: boolean,
): IUserGetFailAction {
): TUserGetFailAction {
return { type: UserTypes.USER_GET_FAIL, payload: { error, logout } };
}
export function userPassChange(password: string): IUserPassChangeAction {
export function userPassChange(password: string): TUserPassChangeAction {
return { type: UserTypes.USER_PASS_CHANGE, password };
}
export function userPassChangeSuccess(
user: IUserAuthJSON,
): IUserPassChangeSuccessAction {
user: TUserAuthJSON,
): TUserPassChangeSuccessAction {
showPasswordSavedToast();
return { type: UserTypes.USER_PASS_CHANGE_SUCCESS, payload: user };
}
@@ -87,7 +87,7 @@ export function userPassChangeSuccess(
export function userPassChangeFail(
error: string,
logout: boolean,
): IUserPassChangeFailAction {
): TUserPassChangeFailAction {
showPasswordNotSavedToast(error);
return {
type: UserTypes.USER_PASS_CHANGE_FAIL,
@@ -96,10 +96,10 @@ export function userPassChangeFail(
}
export type UserAction =
| IUserGetAction
| IUserGetSuccessAction
| IUserGetFailAction
| IUserLogoutAction
| IUserPassChangeAction
| IUserPassChangeFailAction
| IUserPassChangeSuccessAction;
| TUserGetAction
| TUserGetSuccessAction
| TUserGetFailAction
| TUserLogoutAction
| TUserPassChangeAction
| TUserPassChangeFailAction
| TUserPassChangeSuccessAction;

View File

@@ -1,20 +1,20 @@
import { Reducer } from "react";
import { IUserJSON } from "~/src/shared/types";
import { TUserJSON } from "~/src/shared/types";
import { AuthAction, AuthTypes } from "~src/redux/auth/actions";
import { UserAction, UserTypes } from "./actions";
export interface IUserState {
user: IUserJSON | null;
export interface TUserState {
user: TUserJSON | null;
}
const defaultUserState: IUserState = {
const defaultUserState: TUserState = {
user: null,
};
export const userReducer: Reducer<IUserState, AuthAction> = (
state: IUserState = defaultUserState,
export const userReducer: Reducer<TUserState, AuthAction> = (
state: TUserState = defaultUserState,
action: AuthAction | UserAction,
): IUserState => {
): TUserState => {
switch (action.type) {
case AuthTypes.AUTH_SUCCESS:
case UserTypes.USER_GET_SUCCESS:

View File

@@ -4,7 +4,7 @@ import { changeUserPassword, fetchUser } from "~src/redux/api/user";
import {
getUserFail,
getUserSuccess,
IUserPassChangeAction,
TUserPassChangeAction,
userPassChangeFail,
userPassChangeSuccess,
UserTypes,
@@ -32,7 +32,7 @@ function* getUser() {
}
}
function* userPassChange(action: IUserPassChangeAction) {
function* userPassChange(action: TUserPassChangeAction) {
try {
const { response, timeout } = yield race({
response: call(changeUserPassword, action.password),