mirror of
https://github.com/usatiuk/photos.git
synced 2025-10-28 07:27:47 +01:00
more type safety with zod
This commit is contained in:
99
frontend/package-lock.json
generated
99
frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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}`,
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]]);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user