setShow(false)}
+ />
+
+ setInput({ search: e.target.value })}
+ onFocus={() => setInput({ focus: true })}
+ onBlur={() => setInput({ focus: false })}
+ autoFocus
+ />
+
+ {input.search && input.results.length ? (
+
+ ) : null}
+ >
+ ) : null}
+ >
+ );
+
+ // return (
+ //
+ //
+ // setSearchInput(e.target.value)}
+ // onFocus={() => setIsFocused(true)}
+ // onBlur={() => setIsFocused(false)}
+ // />
+ //
+ //
+ //
+ // );
+};
+
+export default Search;
\ No newline at end of file
diff --git a/frontend/components/Search/Homepage/Search.jsx b/frontend/components/Search/Homepage/Search.tsx
similarity index 83%
rename from frontend/components/Search/Homepage/Search.jsx
rename to frontend/components/Search/Homepage/Search.tsx
index 4cd735b2..d63c26b4 100644
--- a/frontend/components/Search/Homepage/Search.jsx
+++ b/frontend/components/Search/Homepage/Search.tsx
@@ -1,100 +1,109 @@
-import styles from "./Search.module.css";
-import { useEffect, useReducer, useState } from "react";
-
-import axios from "axios";
-import useSWR from "swr";
-
-import Link from "next/link";
-import { font } from "@fonts";
-
-const server = process.env.NEXT_PUBLIC_SERVER;
-const fetcher = (url, query, limit) =>
- axios
- .get(url, { params: { q: query, limit } })
- .then((res) => res.data)
- .catch((e) => console.error(e));
-
-const Search = () => {
- const [input, setInput] = useReducer(
- (prev, next) => {
- return { ...prev, ...next };
- },
- {
- search: "",
- results: [],
- focus: false,
- }
- );
-
- const limit = 5;
- const { data } = useSWR(
- input.search ? [server + "/filers/search", input.search, limit] : null,
- ([url, query, limit]) => fetcher(url, query, limit),
- {
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- }
- );
- useEffect(() => {
- if (data) {
- setInput({ results: data.results });
- } else {
- setInput({ results: [] });
- }
- }, [data]);
-
- return (
-
-
- setInput({ search: e.target.value })}
- onFocus={() => setInput({ focus: true })}
- onBlur={() => setInput({ focus: false })}
- />
-
-
-
-
- );
-};
-
-export default Search;
+import styles from "./Search.module.css";
+import { useEffect, useReducer, useState } from "react";
+import axios from "axios";
+import useSWR from "swr";
+import Link from "next/link";
+import { font } from "@fonts";
+
+const server: string | undefined = process.env.NEXT_PUBLIC_SERVER;
+
+interface Result {
+ cik: string;
+ name: string;
+ tickers: string[];
+}
+
+const fetcher = (url: string, query: string, limit: number) =>
+ axios
+ .get(url, { params: { q: query, limit } })
+ .then((res) => res.data)
+ .catch((e) => console.error(e));
+
+interface InputState {
+ search: string;
+ results: Result[];
+ focus: boolean;
+}
+
+const Search = () => {
+ const [input, setInput] = useReducer(
+ (prev: InputState, next: Partial
) => {
+ return { ...prev, ...next };
+ },
+ {
+ search: "",
+ results: [],
+ focus: false,
+ }
+ );
+
+ const limit = 5;
+ const { data } = useSWR(
+ input.search ? [server + "/filers/search", input.search, limit] : null,
+ ([url, query, limit]: [string, string, number]) => fetcher(url, query, limit),
+ {
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ }
+ );
+ useEffect(() => {
+ if (data) {
+ setInput({ results: data.results });
+ } else {
+ setInput({ results: [] });
+ }
+ }, [data]);
+
+ return (
+
+
+ setInput({ search: e.target.value })}
+ onFocus={() => setInput({ focus: true })}
+ onBlur={() => setInput({ focus: false })}
+ />
+
+
+
+ );
+};
+
+export default Search;
\ No newline at end of file
diff --git a/frontend/components/Source/Source.jsx b/frontend/components/Source/Source.tsx
similarity index 69%
rename from frontend/components/Source/Source.jsx
rename to frontend/components/Source/Source.tsx
index ddd75541..c4fec512 100644
--- a/frontend/components/Source/Source.jsx
+++ b/frontend/components/Source/Source.tsx
@@ -2,14 +2,25 @@ import styles from "./Source.module.css";
import SourceIcon from "@/public/static/contact.svg";
-const Source = (props) => {
+import React from "react";
+
+interface SourceProps {
+ cik?: string;
+ color?: "dark" | "light";
+ link?: string;
+ width?: string;
+ marginLeft?: string;
+ className?: string;
+}
+
+const Source: React.FC = (props) => {
const cik = props.cik || null;
const color = props.color || "dark";
const link =
props.link ||
(cik
? "https://www.sec.gov/cgi-bin/browse-edgar?" +
- new URLSearchParams({ CIK: cik.padStart(10, 0) })
+ new URLSearchParams({ CIK: cik.padStart(10, "0") })
: null);
const width = props.width || "20px";
const marginLeft = props.marginLeft || "";
@@ -28,4 +39,4 @@ const Source = (props) => {
);
};
-export default Source;
+export default Source;
\ No newline at end of file
diff --git a/frontend/components/Table/Header/Header.jsx b/frontend/components/Table/Header/Header.tsx
similarity index 79%
rename from frontend/components/Table/Header/Header.jsx
rename to frontend/components/Table/Header/Header.tsx
index ab6831e2..402398ec 100644
--- a/frontend/components/Table/Header/Header.jsx
+++ b/frontend/components/Table/Header/Header.tsx
@@ -1,55 +1,70 @@
-import styles from "./Header.module.css";
-import tableStyles from "../Table.module.css";
-
-import { font } from "@fonts";
-
-import Sort from "./sort.svg";
-
-const Header = (props) => {
- const headers = props.headers;
- const sort = props.sort;
- const reverse = props.reverse;
- const activateHeader = (accessor, direction) =>
- props.activate(accessor, direction);
-
- return (
-
- {headers
- .filter((h) => h.active)
- .map((h) => {
- return (
- |
-
-
- |
- );
- })}
-
- );
-};
-
-export default Header;
+import styles from "./Header.module.css";
+import tableStyles from "../Table.module.css";
+
+import { font } from "@fonts";
+
+import Sort from "./sort.svg";
+
+import React from "react";
+
+interface HeaderProps {
+ headers: Header[];
+ sort: string;
+ reverse: boolean;
+ activate: (accessor: string, direction: boolean) => void;
+}
+
+interface Header {
+ display: string;
+ sort: string;
+ active: boolean;
+}
+
+const Header: React.FC = (props) => {
+ const headers = props.headers;
+ const sort = props.sort;
+ const reverse = props.reverse;
+ const activateHeader = (accessor: string, direction: boolean) =>
+ props.activate(accessor, direction);
+
+ return (
+
+ {headers
+ .filter((h) => h.active)
+ .map((h) => {
+ return (
+ |
+
+
+ |
+ );
+ })}
+
+ );
+};
+
+export default Header;
\ No newline at end of file
diff --git a/frontend/components/Table/Pagination/Count/Count.jsx b/frontend/components/Table/Pagination/Count/Count.tsx
similarity index 66%
rename from frontend/components/Table/Pagination/Count/Count.jsx
rename to frontend/components/Table/Pagination/Count/Count.tsx
index 8c42ef39..6e9138c2 100644
--- a/frontend/components/Table/Pagination/Count/Count.jsx
+++ b/frontend/components/Table/Pagination/Count/Count.tsx
@@ -1,11 +1,21 @@
import styles from "../Pagination.module.css";
-import { useEffect, useState } from "react";
-
+import { useEffect, useState, ChangeEvent, FocusEvent, KeyboardEvent } from "react";
import { font } from "@fonts";
-const Count = (props) => {
+interface Pagination {
+ count: number;
+ limit: number;
+ offset: number;
+}
+
+interface CountProps {
+ pagination: Pagination;
+ skip: (offset: number) => void;
+}
+
+const Count: React.FC = (props) => {
const pagination = props.pagination;
- const setOffset = (o) => props.skip(o);
+ const setOffset = (o: number) => props.skip(o);
const totalPageCount = Math.ceil(pagination.count / pagination.limit);
const realPageCount = Math.floor(pagination.offset / pagination.limit) + 1;
@@ -23,8 +33,8 @@ const Count = (props) => {
}
setFocus(false);
};
- const handleChange = (e) =>
- isNaN(e.target.value) ? null : setPageCount(Number(e.target.value));
+ const handleChange = (e: ChangeEvent) =>
+ isNaN(Number(e.target.value)) ? null : setPageCount(Number(e.target.value));
return (
@@ -38,7 +48,7 @@ const Count = (props) => {
onChange={(e) => handleChange(e)}
type="text"
value={focus ? pageCount : realPageCount}
- onKeyDown={(e) => (e.key === "Enter" ? e.target.blur() : null)}
+ onKeyDown={(e: KeyboardEvent
) => (e.key === "Enter" ? e.currentTarget.blur() : null)}
/>
of {totalPageCount}
@@ -47,4 +57,4 @@ const Count = (props) => {
);
};
-export default Count;
+export default Count;
\ No newline at end of file
diff --git a/frontend/components/Table/Pagination/Limit/Limit.jsx b/frontend/components/Table/Pagination/Limit/Limit.tsx
similarity index 60%
rename from frontend/components/Table/Pagination/Limit/Limit.jsx
rename to frontend/components/Table/Pagination/Limit/Limit.tsx
index 618a437f..ab1c7f50 100644
--- a/frontend/components/Table/Pagination/Limit/Limit.jsx
+++ b/frontend/components/Table/Pagination/Limit/Limit.tsx
@@ -1,11 +1,21 @@
import styles from "../Pagination.module.css";
-import { useEffect, useState } from "react";
+import { useEffect, useState, ChangeEvent, FocusEvent, KeyboardEvent } from "react";
import { font } from "@fonts";
-const Limit = (props) => {
+interface PaginationProps {
+ pagination: Pagination;
+ paginate: (p: number) => void;
+}
+
+interface Pagination {
+ count: number;
+ limit: number;
+}
+
+const Limit = (props: PaginationProps) => {
const pagination = props.pagination;
- const setPagination = (p) => props.paginate(p);
+ const setPagination = (p: number) => props.paginate(p);
const [focus, setFocus] = useState(false);
const [paginationLimit, setPaginationLimit] = useState(100);
@@ -16,7 +26,7 @@ const Limit = (props) => {
setPagination(pagination.count);
setFocus(false);
}
- }, []);
+ }, [pagination.count, setPagination]);
const handleBlur = () => {
if (paginationLimit > 0) {
@@ -24,8 +34,8 @@ const Limit = (props) => {
}
setFocus(false);
};
- const handleChange = (e) =>
- isNaN(e.target.value) ? null : setPaginationLimit(Number(e.target.value));
+ const handleChange = (e: ChangeEvent) =>
+ isNaN(e.target.value as any) ? null : setPaginationLimit(Number(e.target.value));
return (
@@ -36,7 +46,7 @@ const Limit = (props) => {
onChange={(e) => handleChange(e)}
type="text"
value={focus ? paginationLimit : pagination.limit}
- onKeyDown={(e) => (e.key === "Enter" ? e.target.blur() : null)}
+ onKeyDown={(e: KeyboardEvent
) => (e.key === "Enter" ? (e.target as HTMLInputElement).blur() : null)}
/>
of {pagination.count}
@@ -45,4 +55,4 @@ const Limit = (props) => {
);
};
-export default Limit;
+export default Limit;
\ No newline at end of file
diff --git a/frontend/components/Table/Pagination/Pagination.jsx b/frontend/components/Table/Pagination/Pagination.tsx
similarity index 73%
rename from frontend/components/Table/Pagination/Pagination.jsx
rename to frontend/components/Table/Pagination/Pagination.tsx
index 986564b6..77923582 100644
--- a/frontend/components/Table/Pagination/Pagination.jsx
+++ b/frontend/components/Table/Pagination/Pagination.tsx
@@ -6,11 +6,23 @@ import RightIcon from "@/public/static/left.svg";
import Count from "./Count/Count";
import Limit from "./Limit/Limit";
-const Pagination = (props) => {
+interface PaginationProps {
+ pagination: PaginationData;
+ paginate: (page: number) => void;
+ skip: (offset: number) => void;
+}
+
+interface PaginationData {
+ offset: number;
+ limit: number;
+ count: number;
+}
+
+const Pagination: React.FC = (props) => {
const pagination = props.pagination;
- const paginate = (p) => props.paginate(p);
- const skip = (o) => props.skip(o);
+ const paginate = (p: number) => props.paginate(p);
+ const skip = (o: number) => props.skip(o);
const leftOffset = Number(pagination.offset - pagination.limit);
const rightOffset = Number(pagination.offset + pagination.limit);
@@ -37,4 +49,4 @@ const Pagination = (props) => {
) : null;
};
-export default Pagination;
+export default Pagination;
\ No newline at end of file
diff --git a/frontend/components/Table/Row/Row.jsx b/frontend/components/Table/Row/Row.tsx
similarity index 75%
rename from frontend/components/Table/Row/Row.jsx
rename to frontend/components/Table/Row/Row.tsx
index 67d66dbe..4a207aa5 100644
--- a/frontend/components/Table/Row/Row.jsx
+++ b/frontend/components/Table/Row/Row.tsx
@@ -1,34 +1,50 @@
-import tableStyles from "../Table.module.css";
-
-import { font } from "@fonts";
-
-const Row = (props) => {
- const item = props.item;
- const headers = props.headers;
- return (
-
- {headers
- .filter((h) => h.active)
- .map((h) => {
- const display = item[h.accessor];
- return (
- |
- {display}
- |
- );
- })}
-
- );
-};
-
-export default Row;
+import tableStyles from "../Table.module.css";
+
+import { font } from "@fonts";
+
+interface Header {
+ accessor: string;
+ display: string;
+ active: boolean;
+}
+
+interface Item {
+ cusip: string;
+ [key: string]: any;
+}
+
+interface RowProps {
+ item: Item;
+ headers: Header[];
+}
+
+const Row: React.FC = (props) => {
+ const item = props.item;
+ const headers = props.headers;
+ return (
+
+ {headers
+ .filter((h) => h.active)
+ .map((h) => {
+ const display = item[h.accessor];
+ return (
+ |
+ {display}
+ |
+ );
+ })}
+
+ );
+};
+
+export default Row;
\ No newline at end of file
diff --git a/frontend/components/Table/Table.jsx b/frontend/components/Table/Table.tsx
similarity index 68%
rename from frontend/components/Table/Table.jsx
rename to frontend/components/Table/Table.tsx
index cd8c4d89..58c44c14 100644
--- a/frontend/components/Table/Table.jsx
+++ b/frontend/components/Table/Table.tsx
@@ -6,18 +6,30 @@ import Row from "./Row/Row";
import Header from "./Header/Header";
import Pagination from "./Pagination/Pagination";
-const Table = (props) => {
+type TableProps = {
+ items: Array<{ id: string; [key: string]: any }>;
+ loading?: boolean;
+ headers: Array;
+ sort: string;
+ reverse: boolean;
+ activate: (accessor: string, direction: boolean) => void;
+ pagination: { sold: boolean; na: boolean; [key: string]: any };
+ paginate: (p: number) => void;
+ skip: (o: number) => void;
+};
+
+const Table: React.FC = (props) => {
const items = props.items;
const loading = props.loading || false;
const headers = props.headers;
const sort = props.sort;
const reverse = props.reverse;
- const activate = (accessor, direction) => props.activate(accessor, direction);
+ const activate = (accessor: string, direction: boolean) => props.activate(accessor, direction);
const pagination = props.pagination;
- const paginate = (p) => props.paginate(p);
- const skip = (o) => props.skip(o);
+ const paginate = (p: number) => props.paginate(p);
+ const skip = (o: number) => props.skip(o);
return (
<>
@@ -51,4 +63,4 @@ const Table = (props) => {
);
};
-export default Table;
+export default Table;
\ No newline at end of file
diff --git a/frontend/components/Tabs/Tabs.jsx b/frontend/components/Tabs/Tabs.tsx
similarity index 74%
rename from frontend/components/Tabs/Tabs.jsx
rename to frontend/components/Tabs/Tabs.tsx
index a8beb474..6a1bca08 100644
--- a/frontend/components/Tabs/Tabs.jsx
+++ b/frontend/components/Tabs/Tabs.tsx
@@ -7,14 +7,20 @@ import { selectTab, setTab } from "@/redux/filerSlice";
import { font, fontLight } from "@fonts";
-const Tab = (props) => {
- const id = props.id;
- const tab = props.tab;
- const index = props.index + 1;
- const handleTab = props.handleTab;
+import React from "react";
- const length = props.length;
- const hint = props.hint || null;
+interface TabProps {
+ id: string;
+ tab: string;
+ index: number;
+ handleTab: (id: string) => void;
+ length: number;
+ hint?: string | null;
+ title: string;
+}
+
+const Tab: React.FC = (props) => {
+ const { id, tab, index, handleTab, length, hint, title } = props;
return (
{
].join(" ")}
onClick={() => handleTab(id)}
>
- {props.title}
+ {title}
{hint ? (
@@ -38,18 +44,24 @@ const Tab = (props) => {
);
};
-const tabs = [
+interface TabData {
+ title: string;
+ hint: string;
+ id: string;
+}
+
+const tabs: TabData[] = [
{ title: "Stocks", hint: "Table", id: "stocks" },
// { title: "Charts", hint: "Graphs", id: "charts" },
{ title: "Filings", hint: "Comparisons", id: "filings" },
];
-const Tabs = () => {
+const Tabs: React.FC = () => {
const tab = useSelector(selectTab);
const dispatch = useDispatch();
const router = useRouter();
- const handleTab = (value) => {
+ const handleTab = (value: string) => {
router.query.tab = value;
router.push(router);
dispatch(setTab(value));
@@ -73,4 +85,4 @@ const Tabs = () => {
);
};
-export default Tabs;
+export default Tabs;
\ No newline at end of file
diff --git a/frontend/components/Tip/Tip.jsx b/frontend/components/Tip/Tip.tsx
similarity index 69%
rename from frontend/components/Tip/Tip.jsx
rename to frontend/components/Tip/Tip.tsx
index 398644cc..be0e3678 100644
--- a/frontend/components/Tip/Tip.jsx
+++ b/frontend/components/Tip/Tip.tsx
@@ -2,7 +2,12 @@ import styles from "./Tip.module.css";
import { fontLight } from "@fonts";
-const Tip = (props) => {
+interface TipProps {
+ top?: number | null;
+ text: string;
+}
+
+const Tip: React.FC = (props) => {
const top = props.top || null;
return (
{
);
};
-export default Tip;
+export default Tip;
\ No newline at end of file
diff --git a/frontend/components/Unavailable/Unavailable.jsx b/frontend/components/Unavailable/Unavailable.tsx
similarity index 80%
rename from frontend/components/Unavailable/Unavailable.jsx
rename to frontend/components/Unavailable/Unavailable.tsx
index 6329db46..202246ce 100644
--- a/frontend/components/Unavailable/Unavailable.jsx
+++ b/frontend/components/Unavailable/Unavailable.tsx
@@ -6,7 +6,15 @@ import { font } from "@fonts";
import useEllipsis from "components/Hooks/useEllipsis";
-const Unavailable = (props) => {
+import React from "react";
+
+type UnavailableProps = {
+ type?: "stocks" | "loading";
+ cik?: string;
+ text?: string;
+};
+
+const Unavailable: React.FC = (props) => {
const type = props.type || "stocks";
const cik = type == "stocks" ? props.cik : null;
const text = props.text || null;
@@ -25,7 +33,7 @@ const Unavailable = (props) => {
{
);
};
-export default Unavailable;
+export default Unavailable;
\ No newline at end of file
diff --git a/frontend/components/fonts/index.js b/frontend/components/fonts/index.js
deleted file mode 100644
index 30b4f487..00000000
--- a/frontend/components/fonts/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Inter } from "@next/font/google";
-
-const font = Inter({ weight: "800", subsets: ["latin"] });
-const fontBold = Inter({ weight: "900", subsets: ["latin"] });
-const fontLight = Inter({ weight: "700", subsets: ["latin"] });
-
-export { font, fontBold, fontLight };
diff --git a/frontend/components/fonts/index.ts b/frontend/components/fonts/index.ts
new file mode 100644
index 00000000..42aadb10
--- /dev/null
+++ b/frontend/components/fonts/index.ts
@@ -0,0 +1,15 @@
+import { Inter } from "@next/font/google";
+
+type FontWeight = "700" | "800" | "900";
+
+interface FontOptions {
+ weight: FontWeight;
+ subsets: string[];
+ className: string;
+}
+
+const font: FontOptions = Inter({ weight: "800", subsets: ["latin"] });
+const fontBold: FontOptions = Inter({ weight: "900", subsets: ["latin"] });
+const fontLight: FontOptions = Inter({ weight: "700", subsets: ["latin"] });
+
+export { font, fontBold, fontLight };
\ No newline at end of file
diff --git a/frontend/next.config.js b/frontend/next.config.mjs
similarity index 67%
rename from frontend/next.config.js
rename to frontend/next.config.mjs
index 58863662..58804fbb 100644
--- a/frontend/next.config.js
+++ b/frontend/next.config.mjs
@@ -1,32 +1,33 @@
-/** @type {import('next').NextConfig} */
-
-const nextConfig = {
- reactStrictMode: true,
- webpack(config) {
- const fileLoaderRule = config.module.rules.find((rule) =>
- rule.test?.test?.(".svg")
- );
-
- config.module.rules.push(
- {
- ...fileLoaderRule,
- test: /\.svg$/i,
- resourceQuery: /url/,
- },
- {
- test: /\.svg$/i,
- issuer: /\.[jt]sx?$/,
- resourceQuery: { not: /url/ },
- use: ["@svgr/webpack"],
- }
- );
- fileLoaderRule.exclude = /\.svg$/i;
- return config;
- },
- env: {
- NEXT_PUBLIC_SERVER: "https://content.wallstreetlocal.com",
- },
- output: "standalone",
-};
-
-module.exports = nextConfig;
+const nextConfig = {
+ reactStrictMode: true,
+ webpack(config) {
+ const fileLoaderRule = config.module?.rules?.find((rule) =>
+ rule.test?.test?.(".svg")
+ );
+
+ config.module?.rules?.push(
+ {
+ ...fileLoaderRule,
+ test: /\.svg$/i,
+ resourceQuery: /url/,
+ },
+ {
+ test: /\.svg$/i,
+ issuer: /\.[jt]sx?$/,
+ resourceQuery: { not: /url/ },
+ use: ["@svgr/webpack"],
+ }
+ );
+ if (fileLoaderRule) {
+ fileLoaderRule.exclude = /\.svg$/i;
+ }
+ return config;
+ },
+ env: {
+ NEXT_PUBLIC_SERVER: "https://content.wallstreetlocal.com",
+ },
+ output: "standalone",
+ reactStrictMode: true,
+};
+
+export default nextConfig;
diff --git a/frontend/package.json b/frontend/package.json
index 856eead7..a8bb4a87 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "type-check": "tsc --noEmit"
},
"dependencies": {
"@dnd-kit/core": "^6.0.8",
@@ -39,10 +40,13 @@
"swr": "^2.1.1"
},
"devDependencies": {
+ "@types/node": "^18.11.18",
"@types/react": "18.0.28",
"@vercel/analytics": "^1.1.1",
"eslint": "8.34.0",
- "eslint-config-next": "13.1.6"
+ "eslint-config-next": "13.1.6",
+ "typescript": "^4.9.4",
+ "url-loader": "^4.1.1"
},
"description": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).",
"main": "next.config.js",
@@ -56,4 +60,4 @@
"url": "https://github.com/leftmove/whale-project/issues"
},
"homepage": "https://github.com/leftmove/whale-project#readme"
-}
+}
\ No newline at end of file
diff --git a/frontend/pages/_app.js b/frontend/pages/_app.js
deleted file mode 100644
index f776283c..00000000
--- a/frontend/pages/_app.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import "@/styles/globals.css";
-
-import Layout from "components/Layouts/Layout";
-
-import { Inter } from "@next/font/google";
-
-const font = Inter({ weight: "800", subsets: ["latin"] });
-const fontBold = Inter({ weight: "900", subsets: ["latin"] });
-const fontLight = Inter({ weight: "700", subsets: ["latin"] });
-
-function App(props) {
- const getLayout =
- props.Component.getLayout ||
- (() => {});
-
- return getLayout(props);
-}
-
-export default App;
-export { font, fontBold, fontLight };
\ No newline at end of file
diff --git a/frontend/pages/_app.tsx b/frontend/pages/_app.tsx
new file mode 100644
index 00000000..efb00294
--- /dev/null
+++ b/frontend/pages/_app.tsx
@@ -0,0 +1,18 @@
+import "@/styles/globals.css";
+
+import Layout from "components/Layouts/Layout";
+
+interface AppProps {
+ Component: React.ComponentType & { getLayout?: (props: any) => JSX.Element };
+ pageProps: any;
+}
+
+function App(props: AppProps): JSX.Element {
+ const getLayout =
+ props.Component.getLayout ||
+ (() => {});
+
+ return getLayout(props);
+}
+
+export default App;
\ No newline at end of file
diff --git a/frontend/pages/_document.js b/frontend/pages/_document.tsx
similarity index 69%
rename from frontend/pages/_document.js
rename to frontend/pages/_document.tsx
index af632100..54be7681 100644
--- a/frontend/pages/_document.js
+++ b/frontend/pages/_document.tsx
@@ -1,13 +1,14 @@
-import { Html, Head, Main, NextScript } from "next/document";
-
-export default function Document() {
- return (
-
-
-
-
-
-
-
- );
-}
+import { Html, Head, Main, NextScript } from "next/document";
+import { FC } from "react";
+const Document: FC = () => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+export default Document;
\ No newline at end of file
diff --git a/frontend/pages/about/contact.jsx b/frontend/pages/about/contact.tsx
similarity index 95%
rename from frontend/pages/about/contact.jsx
rename to frontend/pages/about/contact.tsx
index d9c4b37b..254d143a 100644
--- a/frontend/pages/about/contact.jsx
+++ b/frontend/pages/about/contact.tsx
@@ -1,49 +1,49 @@
-import styles from "@/styles/Contact.module.css";
-
-import Head from "next/head";
-import { font } from "@fonts";
-
-import MailSVG from "@/images/envelope.svg";
-import DiscordSVG from "@/images/discord.svg";
-import LinkedInSVG from "@/images/linkedin.svg";
-
-export default function Contact() {
- return (
- <>
-
- wallstreetlocal | Contact
-
-
-
Donation and Links
-
-
- {/*
Info */}
-
-
-
- 100anonyo@gmail.com
-
-
-
-
- zipped1
-
-
-
-
- anonyo-noor-272540249
-
-
-
-
-
-
- >
- );
-}
+import styles from "@/styles/Contact.module.css";
+
+import Head from "next/head";
+import { font } from "@fonts";
+
+import MailSVG from "@/images/envelope.svg";
+import DiscordSVG from "@/images/discord.svg";
+import LinkedInSVG from "@/images/linkedin.svg";
+
+export default function Contact(): JSX.Element {
+ return (
+ <>
+
+ wallstreetlocal | Contact
+
+
+
Donation and Links
+
+
+ {/*
Info */}
+
+
+
+ 100anonyo@gmail.com
+
+
+
+
+ zipped1
+
+
+
+
+ anonyo-noor-272540249
+
+
+
+
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/frontend/pages/about/resources.jsx b/frontend/pages/about/resources.tsx
similarity index 96%
rename from frontend/pages/about/resources.jsx
rename to frontend/pages/about/resources.tsx
index 9c4111a7..49c33651 100644
--- a/frontend/pages/about/resources.jsx
+++ b/frontend/pages/about/resources.tsx
@@ -1,194 +1,193 @@
-import styles from "@/styles/Resources.module.css";
-
-import Head from "next/head";
-import Link from "next/link";
-import { font } from "@fonts";
-
-export default function Resources() {
- return (
- <>
-
- wallstreetlocal | Resources
- {" "}
-
-
-
- Resources
-
-
- All data used is publicly available via the SEC. The following
- contains useful links and information about SEC registered
- companies.
-
-
-
-
-
-
-
-
- (Add 13F to the filing category field to find companies that have
- filed 13F filings)
-
-
-
-
-
-
-
- (Bulk data via the SEC)
-
-
-
-
-
-
-
- (Bulk data via the SEC)
-
-
-
-
-
-
-
- (Bulk data via the SEC)
-
-
-
-
-
-
-
- (Explanations for different form types)
-
-
-
-
-
-
-
- (Search database for companies, created by wallstreetlocal,
- formatted in BSON)
-
-
-
-
-
-
-
- (Gist listing popular filers, taken from various sources. This is
- where the popular filers page gets its list from.)
-
-
-
-
-
-
-
- (Gist listing top filers, taken from various sources. This is
- where the top filers page gets its list from.)
-
-
-
-
- While not all data collected and formatted by wallstreetlocal is
- available by bulk because of data costs, you can download resources
- for individual filers by visiting their respective pages.
-
-
- >
- );
-}
+import styles from "@/styles/Resources.module.css";
+import Head from "next/head";
+import Link from "next/link";
+import { font } from "@fonts";
+import React from "react";
+export default function Resources(): JSX.Element {
+ return (
+ <>
+
+ wallstreetlocal | Resources
+
+
+
+
+ Resources
+
+
+ All data used is publicly available via the SEC. The following
+ contains useful links and information about SEC registered
+ companies.
+
+
+
+
+
+
+
+
+ (Add 13F to the filing category field to find companies that have
+ filed 13F filings)
+
+
+
+
+
+
+
+ (Bulk data via the SEC)
+
+
+
+
+
+
+
+ (Bulk data via the SEC)
+
+
+
+
+
+
+
+ (Bulk data via the SEC)
+
+
+
+
+
+
+
+ (Explanations for different form types)
+
+
+
+
+
+
+
+ (Search database for companies, created by wallstreetlocal,
+ formatted in BSON)
+
+
+
+
+
+
+
+ (Gist listing popular filers, taken from various sources. This is
+ where the popular filers page gets its list from.)
+
+
+
+
+
+
+
+ (Gist listing top filers, taken from various sources. This is
+ where the top filers page gets its list from.)
+
+
+
+
+ While not all data collected and formatted by wallstreetlocal is
+ available by bulk because of data costs, you can download resources
+ for individual filers by visiting their respective pages.
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/frontend/pages/filers/[cik].jsx b/frontend/pages/filers/[cik].tsx
similarity index 81%
rename from frontend/pages/filers/[cik].jsx
rename to frontend/pages/filers/[cik].tsx
index ce401e99..a2428cc5 100644
--- a/frontend/pages/filers/[cik].jsx
+++ b/frontend/pages/filers/[cik].tsx
@@ -1,100 +1,100 @@
-import axios from "axios";
-
-import { Provider } from "react-redux";
-import { wrapper } from "@/redux/store";
-
-import Layout from "components/Layouts/Layout";
-import Info from "components/Filer/Info";
-import Other from "components/Filer/Other";
-import Building from "components/Filer/Building";
-
-const Filer = (props) => {
- const query = props.query;
- const cik = props.cik;
-
- const continuous = props.continuous;
- const persist = props.persist;
- const tab = props.tab;
-
- console.log(cik);
-
- if (query.building || persist) {
- return ;
- }
-
- if (query.ok || query.continuous || continuous) {
- return ;
- }
-
- if (query.error) {
- return ;
- }
-};
-
-const server = process.env.NEXT_PUBLIC_SERVER;
-export async function getServerSideProps(context) {
- const cik = context.query.cik || null;
- const continuous = context.query.continuous || null;
- const persist = context.query.persist || null;
- const tab = context.query.tab || "stocks";
-
- const query = {
- ok: false,
- building: false,
- continuous: false,
- error: false,
- };
-
- await axios
- .get(server + "/filers/query", {
- params: { cik },
- validateStatus: (status) => status < 500,
- })
- .then((r) => {
- switch (r?.status) {
- case 302:
- query.continuous = true;
- break;
- case 201:
- case 409:
- query.building = true;
- break;
- case 200:
- query.ok = true;
- break;
- default:
- query.error = true;
- break;
- }
- })
- .catch((e) => {
- console.error(e);
- query.error = true;
- });
-
- return {
- props: {
- query,
- cik,
- tab,
- persist,
- continuous,
- },
- };
-}
-
-Filer.getLayout = function getLayout({ Component, ...rest }) {
- const { store, props } = wrapper.useWrappedStore(rest);
- const { cik } = props.pageProps;
- const reduxStore = { ...store, filer: { ...store.filer, cik: cik } };
-
- return (
-
-
-
-
-
- );
-};
-
-export default Filer;
+import axios from "axios";
+import { Provider } from "react-redux";
+import { wrapper } from "@/redux/store";
+import Layout from "components/Layouts/Layout";
+import Info from "components/Filer/Info";
+import Other from "components/Filer/Other";
+import Building from "components/Filer/Building";
+import { GetServerSideProps } from "next";
+import { FC } from "react";
+interface Query {
+ ok: boolean;
+ building: boolean;
+ continuous: boolean;
+ error: boolean;
+}
+interface FilerProps {
+ query: Query;
+ cik: string | null;
+ continuous: boolean | null;
+ persist: boolean | null;
+ tab: string;
+}
+const Filer: FC = (props) => {
+ const query = props.query;
+ const cik = props.cik;
+ const continuous = props.continuous;
+ const persist = props.persist;
+ const tab = props.tab;
+
+ if (query.building || persist) {
+ return ;
+ }
+ if (query.ok || query.continuous || continuous) {
+ return ;
+ }
+ if (query.error) {
+ return ;
+ }
+};
+const server = process.env.NEXT_PUBLIC_SERVER;
+export const getServerSideProps: GetServerSideProps = async (context) => {
+ const cik = context.query.cik || null;
+ const continuous = context.query.continuous || null;
+ const persist = context.query.persist || null;
+ const tab = context.query.tab || "stocks";
+ const query: Query = {
+ ok: false,
+ building: false,
+ continuous: false,
+ error: false,
+ };
+ await axios
+ .get(server + "/filers/query", {
+ params: { cik },
+ validateStatus: (status) => status < 500,
+ })
+ .then((r) => {
+ switch (r?.status) {
+ case 302:
+ query.continuous = true;
+ break;
+ case 201:
+ case 409:
+ query.building = true;
+ break;
+ case 200:
+ query.ok = true;
+ break;
+ default:
+ query.error = true;
+ break;
+ }
+ })
+ .catch((e) => {
+ console.error(e);
+ query.error = true;
+ });
+ return {
+ props: {
+ query,
+ cik,
+ tab,
+ persist,
+ continuous,
+ },
+ };
+};
+Filer.getLayout = function getLayout({ Component, ...rest }) {
+ const { store, props } = wrapper.useWrappedStore(rest);
+ const { cik } = props.pageProps;
+ const reduxStore = { ...store, filer: { ...store.filer, cik: cik } };
+ return (
+
+
+
+
+
+ );
+};
+export default Filer;
\ No newline at end of file
diff --git a/frontend/pages/index.jsx b/frontend/pages/index.tsx
similarity index 95%
rename from frontend/pages/index.jsx
rename to frontend/pages/index.tsx
index 83666030..a4de2e98 100644
--- a/frontend/pages/index.jsx
+++ b/frontend/pages/index.tsx
@@ -1,176 +1,180 @@
-"use server";
-
-import styles from "@/styles/Home.module.css";
-
-import axios from "axios";
-
-import Head from "next/head";
-import Image from "next/image";
-
-import { font, fontLight, fontBold } from "@fonts";
-
-import Layout from "components/Layouts/Home";
-import Search from "components/Search/Homepage/Search";
-import Recommended from "components/Recommended/Recommended";
-import Health from "components/Health/Health";
-import Hero from "@/images/hero.jpg";
-import FolderIcon from "@/images/folder.svg";
-import FileIcon from "@/images/file.svg";
-import BookIcon from "@/images/book.svg";
-
-export default function Home(props) {
- return (
- <>
-
-
- wallstreetlocal | Advice from the world's biggest investors
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Nothing to search? See filers sorted by popularity and value in
- the top right corner.
-
-
- Thousands of filings from the world's biggest investors.
-
-
- Wall Street's stock portfolio, for free.
-
-
-
-
-
-
-
- Explore historical stock data, directly from the SEC.
-
-
- The Securities and Exchange Commission (SEC) keeps record of every
- company in the United States. Companies whose holdings surpass $100
- million though, are required to file a special type of form: the 13F
- form. This form, filed quarterly, discloses the filer's holdings,
- providing transparency into their investment activities and allowing
- the public and other market participants to monitor them.
-
-
- The problem though, is that these holdings are often cumbersome to
- access, and valuable analysis is often hidden behind a paywall.
- Through wallstreetlocal, the SEC's 13F filers become more
- accessible and open.
-
- {/*
-
-
-
-
- Stock Data
-
-
- Stocks from over 20 years, matched with external data to create
- accurate, consistent, and useful analysis.
-
-
-
-
-
-
-
- SEC Filings
-
-
- Filings directly from the SEC, served in an accessible format.
- Over 20 years of coverage.
-
-
-
-
-
-
-
- Free Forever
-
-
- The entire backlog of the SEC, free and without quotas.
-
-
-
-
*/}
-
- >
- );
-}
-
-const server = process.env.NEXT_PUBLIC_SERVER;
-export async function getServerSideProps() {
- const health = await axios
- .get(server + "/health")
- .then((r) => r.status === 200)
- .then(() => true)
- .catch(() => false);
- return {
- props: {
- health,
- },
- };
-}
-
-Home.getLayout = ({ Component, pageProps }) => (
-
-
-
-);
+"use server";
+
+import styles from "@/styles/Home.module.css";
+
+import axios from "axios";
+
+import Head from "next/head";
+import Image from "next/image";
+
+import { font, fontLight, fontBold } from "@fonts";
+
+import Layout from "components/Layouts/Home";
+import Search from "components/Search/Homepage/Search";
+import Recommended from "components/Recommended/Recommended";
+import Health from "components/Health/Health";
+import Hero from "@/images/hero.jpg";
+import FolderIcon from "@/images/folder.svg";
+import FileIcon from "@/images/file.svg";
+import BookIcon from "@/images/book.svg";
+
+interface HomeProps {
+ health: boolean;
+}
+
+export default function Home(props: HomeProps) {
+ return (
+ <>
+
+
+ wallstreetlocal | Advice from the world's biggest investors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Nothing to search? See filers sorted by popularity and value in
+ the top right corner.
+
+
+ Thousands of filings from the world's biggest investors.
+
+
+ Wall Street's stock portfolio, for free.
+
+
+
+
+
+
+
+ Explore historical stock data, directly from the SEC.
+
+
+ The Securities and Exchange Commission (SEC) keeps record of every
+ company in the United States. Companies whose holdings surpass $100
+ million though, are required to file a special type of form: the 13F
+ form. This form, filed quarterly, discloses the filer's holdings,
+ providing transparency into their investment activities and allowing
+ the public and other market participants to monitor them.
+
+
+ The problem though, is that these holdings are often cumbersome to
+ access, and valuable analysis is often hidden behind a paywall.
+ Through wallstreetlocal, the SEC's 13F filers become more
+ accessible and open.
+
+ {/*
+
+
+
+
+ Stock Data
+
+
+ Stocks from over 20 years, matched with external data to create
+ accurate, consistent, and useful analysis.
+
+
+
+
+
+
+
+ SEC Filings
+
+
+ Filings directly from the SEC, served in an accessible format.
+ Over 20 years of coverage.
+
+
+
+
+
+
+
+ Free Forever
+
+
+ The entire backlog of the SEC, free and without quotas.
+
+
+
+
*/}
+
+ >
+ );
+}
+
+const server = process.env.NEXT_PUBLIC_SERVER;
+export async function getServerSideProps() {
+ const health = await axios
+ .get(server + "/health")
+ .then((r) => r.status === 200)
+ .then(() => true)
+ .catch(() => false);
+ return {
+ props: {
+ health,
+ },
+ };
+}
+
+Home.getLayout = ({ Component, pageProps }: { Component: any; pageProps: any }) => (
+
+
+
+);
\ No newline at end of file
diff --git a/frontend/pages/recommended/searched.jsx b/frontend/pages/recommended/searched.tsx
similarity index 91%
rename from frontend/pages/recommended/searched.jsx
rename to frontend/pages/recommended/searched.tsx
index 5e77d7cc..b9a0e11e 100644
--- a/frontend/pages/recommended/searched.jsx
+++ b/frontend/pages/recommended/searched.tsx
@@ -8,14 +8,31 @@ import axios from "axios";
import { font } from "@fonts";
import { convertTitle } from "components/Filer/Info";
-const headers = [
+interface Header {
+ display: string;
+ accessor: string;
+}
+
+interface Filer {
+ name: string;
+ cik: string;
+ market_value: string;
+ date: string;
+ [key: string]: any;
+}
+
+interface SearchedProps {
+ filers: Filer[];
+}
+
+const headers: Header[] = [
{ display: "Name", accessor: "name" },
{ display: "CIK", accessor: "cik" },
{ display: "Assets Under Management", accessor: "market_value" },
{ display: "Last Updated", accessor: "date" },
];
-const Searched = (props) => {
+const Searched: React.FC = (props) => {
return (
<>
@@ -112,4 +129,4 @@ export async function getServerSideProps() {
};
}
-export default Searched;
+export default Searched;
\ No newline at end of file
diff --git a/frontend/pages/recommended/top.jsx b/frontend/pages/recommended/top.tsx
similarity index 92%
rename from frontend/pages/recommended/top.jsx
rename to frontend/pages/recommended/top.tsx
index ff759693..157a24c0 100644
--- a/frontend/pages/recommended/top.jsx
+++ b/frontend/pages/recommended/top.tsx
@@ -8,14 +8,31 @@ import axios from "axios";
import { font } from "@fonts";
import { convertTitle } from "components/Filer/Info";
-const headers = [
+interface Header {
+ display: string;
+ accessor: string;
+}
+
+interface Filer {
+ name: string;
+ cik: string;
+ market_value: string;
+ date: string;
+ [key: string]: any;
+}
+
+interface TopProps {
+ filers: Filer[];
+}
+
+const headers: Header[] = [
{ display: "Name", accessor: "name" },
{ display: "CIK", accessor: "cik" },
{ display: "Assets Under Management", accessor: "market_value" },
{ display: "Last Updated", accessor: "date" },
];
-const Top = (props) => {
+const Top: React.FC = (props) => {
return (
<>
@@ -122,4 +139,4 @@ export async function getServerSideProps() {
};
}
-export default Top;
+export default Top;
\ No newline at end of file
diff --git a/frontend/redux/filerSlice.js b/frontend/redux/filerSlice.ts
similarity index 77%
rename from frontend/redux/filerSlice.js
rename to frontend/redux/filerSlice.ts
index d91fb74d..749077b5 100644
--- a/frontend/redux/filerSlice.js
+++ b/frontend/redux/filerSlice.ts
@@ -1,659 +1,667 @@
-import { createSlice, createSelector } from "@reduxjs/toolkit";
-import { HYDRATE } from "next-redux-wrapper";
-
-const initialDate = new Date();
-const initialHeaders = [
- {
- display: "Ticker",
- sort: "ticker",
- accessor: "ticker_str",
- active: true,
- tooltip:
- "This is a unique series of letters assigned to a security for trading purposes",
- },
- {
- display: "Name",
- sort: "name",
- accessor: "name",
- active: false,
- tooltip: "The name of the stock.",
- },
- {
- display: "Class",
- sort: "class",
- accessor: "class",
- active: false,
- tooltip:
- "This refers to the rights of a stockholder, including things like voting and dividends.",
- },
- {
- display: "Sector",
- sort: "sector",
- accessor: "sector",
- active: false,
- tooltip: "The broader industry category to which the stock belongs.",
- },
- {
- display: "CUSIP",
- sort: "cusip",
- accessor: "cusip",
- active: false,
- tooltip:
- "A unique identifier assigned to each registered security in the United States and Canada.",
- },
- {
- display: "Shares Held",
- sort: "shares_held",
- accessor: "shares_held_str",
- active: false,
- tooltip: "The number of shares held, or the principal amount of the stock.",
- },
- {
- display: "Market Value",
- sort: "market_value",
- accessor: "market_value_str",
- active: true,
- tooltip: "The value for the shares of the stock the filer owns.",
- },
- {
- display: "% Portfolio",
- sort: "portfolio_percent",
- accessor: "portfolio_str",
- active: true,
- tooltip:
- "The value of this stock's shares divided by the total value of the portfolio, expressed in percent. ( Value of Shares / Value of Portfolio )",
- },
- {
- display: "% Ownership",
- sort: "ownership_percent",
- accessor: "ownership_str",
- active: false,
- tooltip:
- "The number of shares owned, divided by the current total of shares outstanding, expressed in percent. This value is only accurate the most value of shares outstanding. ( Shares Owned / Shares Existing )",
- },
- {
- display: "Sold Date",
- sort: "sold_time",
- accessor: "sold_str",
- active: false,
- tooltip:
- "The date the stock was sold, taken by retrieving the report date of the last SEC filing said stock showed up on. This is only accurate up to the quarter.",
- },
- {
- display: "Buy Date",
- sort: "buy_time",
- accessor: "buy_str",
- active: false,
- tooltip:
- "The date the stock was bought, according to the report date of the first SEC filing it appeared on. This is only accurate up to the quarter, and only the most recent bought date is shown.",
- },
- {
- display: "Price Paid",
- sort: "buy_price",
- accessor: "buy_price_str",
- active: true,
- tooltip:
- "The price paid for the stock, estimated by taking a close price most near the quarter from which the stock was first reported.",
- },
- {
- display: "Recent Price",
- sort: "recent_price",
- accessor: "recent_price_str",
- active: true,
- tooltip:
- "The recent price of the stock. This may be a couple days delayed.",
- },
- {
- display: "% Gain",
- sort: "gain_percent",
- accessor: "gain_str",
- active: true,
- tooltip:
- "The price paid for the stock subtracted from the recent price, and then divided by price paid, expressed in percent. ( ( Recent Price - Price Paid ) / Price Paid )",
- },
- {
- display: "Industry",
- sort: "industry",
- accessor: "industry",
- active: false,
- tooltip:
- "The specific sector or category of the economy in which the stock's company operates.",
- },
- {
- display: "Report Date",
- sort: "report",
- accessor: "report_str",
- active: false,
- tooltip:
- "The report date listed on the SEC filing this stock was taken from.",
- },
-];
-const initialComparisons = initialHeaders.map((h) => {
- switch (h.sort) {
- case "buy_price":
- return { ...h, active: false };
- case "recent_price":
- return { ...h, active: false };
- case "gain_percent":
- return { ...h, active: false };
- default:
- return h;
- }
-});
-const initialSort = {
- sort: "ticker",
- type: "string",
- set: true,
- na: false,
- sold: false,
- reverse: true,
- pagination: 100,
- limit: 100,
- count: 0,
- offset: 0,
-};
-const initialState = {
- cik: "",
- value: [],
- headers: initialHeaders,
- tab: "stocks",
- sort: initialSort,
- filings: [],
- timeline: {
- comparisons: [
- {
- type: "primary",
- access: "",
- filing: {
- time: 0,
- date: "",
- },
- report: {
- time: 0,
- date: "",
- },
- headers: initialComparisons,
- sort: initialSort,
- stocks: [],
- },
- {
- type: "secondary",
- access: "",
- filingTime: 0,
- reportTime: 0,
- filingDate: "",
- reportDate: "",
- headers: initialComparisons,
- sort: initialSort,
- stocks: [],
- },
- ],
- open: false,
- },
- difference: {
- headers: initialComparisons,
- sort: initialSort,
- stocks: [],
- },
- dates: [
- {
- year: initialDate.getFullYear(),
- month: initialDate.getMonth(),
- day: initialDate.getDate(),
- timestamp: initialDate.getTime() / 1000,
- open: false,
- accessor: initialDate.toLocaleDateString(),
- },
- ],
-};
-
-export const filerSlice = createSlice({
- name: "filer",
- initialState,
- reducers: {
- setCik(state, action) {
- Object.keys(initialState).map((k) => {
- state[k] = initialState[k];
- });
- state.cik = action.payload;
-
- return state;
- },
- setTab(state, action) {
- const payload = action.payload;
- state.tab = payload;
-
- return state;
- },
- activateHeader(state, action) {
- const headers = state.headers;
- const payload = action.payload;
- state.headers = headers.map((h) =>
- h.accessor === payload ? { ...h, active: !h.active } : h
- );
-
- return state;
- },
- sortHeader(state, action) {
- const payload = action.payload;
- let type = "string";
- switch (payload.sort) {
- case "name":
- case "sector":
- case "industry":
- case "class":
- case "cusip":
- type = "string";
- break;
- case "shares_held":
- case "market_value":
- case "portfolio_percent":
- case "ownership_percent":
- case "gain_percent":
- case "recent_price":
- case "buy_price":
- case "report":
- case "buy":
- case "sold_time":
- type = "number";
- break;
- case "buy":
- case "report":
- case "sold_time":
- type = "date";
- break;
- default:
- type = typeof payload.sort === "number" ? "number" : "string";
- break;
- }
- state.sort = { ...state.sort, ...payload, type: type };
- return state;
- },
- sortActive(state) {
- const sort = state.sort;
- const set = sort.set;
- state.sort = { ...sort, set: !set };
-
- return state;
- },
- addHeader(state, action) {
- const payload = action.payload;
- const headers = state.headers;
-
- headers.push({
- ...payload,
- active: true,
- });
-
- state.headers = headers;
- return state;
- },
- editHeader(state, action) {
- const payload = action.payload;
- const headers = state.headers.map((h) =>
- h.accessor === payload.accessor ? { ...h, display: payload.display } : h
- );
-
- state.headers = headers;
- return state;
- },
- removeHeader(state, action) {
- const payload = action.payload;
- const headers = state.headers.filter((h) => h.accessor !== payload);
-
- state.headers = headers;
- return state;
- },
- setHeaders(state, action) {
- const payload = action.payload;
- state.headers = payload;
-
- return state;
- },
- sortSold(state) {
- const sort = state.sort;
- const sold = sort.sold;
- state.sort = { ...sort, sold: !sold };
-
- return state;
- },
- sortNa(state) {
- const sort = state.sort;
- const na = sort.na;
- state.sort = { ...sort, na: !na };
-
- return state;
- },
- setStocks(state, action) {
- const stocks = action.payload;
- state.value = stocks;
- return state;
- },
- updateStocks(state, action) {
- const payload = action.payload;
- const field = payload.field;
- const values = payload.values;
-
- let stocks = state.value;
- stocks = stocks.map((stock) => {
- const cusip = stock.cusip;
- const value = values[cusip];
-
- return { ...stock, [field]: value };
- });
-
- state.value = stocks;
- return state;
- },
- addDate(state, action) {
- const dates = state.dates;
- dates.push(action.payload);
- state.dates = dates;
-
- return state;
- },
- removeDate(state, action) {
- const accessor = action.payload;
- const dates = state.dates;
- state.dates = dates.filter((date) => date.accessor !== accessor);
- return state;
- },
- editDate(state, action) {
- const payload = action.payload;
- const dates = state.dates.map((date) => {
- if (payload.accessor === date.accessor) {
- let newDate = new Date(date.year, date.month, date.day);
- switch (payload.type) {
- case "year":
- newDate.setFullYear(payload.value);
- break;
- case "month":
- newDate.setMonth(payload.value);
- break;
- case "day":
- newDate.setDate(payload.value);
- break;
- case "date":
- newDate = new Date(payload.value);
- break;
- default:
- break;
- }
- return {
- year: newDate.getFullYear(),
- month: newDate.getMonth(),
- day: newDate.getDate(),
- timestamp: newDate.getTime() / 1000,
- open: true,
- accessor: date.accessor,
- };
- } else return date;
- });
- state.dates = dates;
- return state;
- },
- updateDates(state, action) {
- const payload = action.payload;
-
- // const over = payload.over;
- // const accessor = payload.accessor;
- // const active = dates.find((d) => d.accessor === accessor);
-
- // const activeIndex = dates.findIndex((d) => d.accessor === accessor);
- // const overIndex = dates.findIndex((d) => d.accessor === over);
-
- // arrayMove()
-
- // dates.splice(activeIndex, 1);
- // dates.splice(overIndex, 0, active);
-
- state.dates = payload;
- return state;
- },
- openDate(state, action) {
- const payload = action.payload;
- const accessor = payload.accessor;
- const dates = state.dates.map((date) =>
- date.accessor === accessor ? { ...date, open: payload.open } : date
- );
- state.dates = dates;
- return state;
- },
- newDate(state) {
- const dates = state.dates;
- const latestDate = dates.at(-1) || {
- year: initialDate.getFullYear(),
- month: initialDate.getMonth(),
- day: initialDate.getDate(),
- timestamp: initialDate.getTime() / 1000,
- open: false,
- accessor: initialDate.toLocaleDateString(),
- };
- const newDate = new Date(latestDate.accessor);
- newDate.setDate(newDate.getDate() + 1);
-
- dates.push({
- year: newDate.getFullYear(),
- month: newDate.getMonth(),
- day: newDate.getDate(),
- timestamp: newDate.getTime() / 1000,
- open: false,
- accessor: newDate.toLocaleDateString(),
- });
-
- state.dates = dates;
- return state;
- },
- setPagination(state, action) {
- state.sort.pagination = action.payload;
- return state;
- },
- setCount(state, action) {
- const sort = state.sort;
- const payload = action.payload;
- const pagination = sort.pagination;
-
- if (pagination < 0) {
- state.sort.pagination = payload > 100 ? 100 : payload;
- }
-
- state.sort.count = payload;
- return state;
- },
- setFilingCount(state, action) {
- const payload = action.payload;
- const type = payload.type;
- const count = payload.count;
-
- const comparisons = state.timeline.comparisons.map((c) =>
- c.type === type
- ? {
- ...c,
- sort: { ...c.sort, pagination: count > 100 ? 100 : count, count },
- }
- : c
- );
-
- state.timeline.comparisons = comparisons;
- return state;
- },
- setOffset(state, action) {
- const payload = action.payload;
- if (payload >= 0) {
- state.sort.offset = Number(payload);
- }
- return state;
- },
- setFilings(state, action) {
- const payload = action.payload;
-
- state.filings = payload;
- return state;
- },
- setPrimary(state, action) {
- const payload = action.payload;
-
- const comparisons = state.timeline.comparisons;
- const primary = comparisons[0];
- state.timeline.comparisons[0] = { ...primary, ...payload };
-
- return state;
- },
- setSecondary(state, action) {
- const payload = action.payload;
-
- const comparisons = state.timeline.comparisons;
- const secondary = comparisons[1];
- state.timeline.comparisons[1] = { ...secondary, ...payload };
-
- return state;
- },
- setComparison(state, action) {
- const payload = action.payload;
- const type = payload.type;
- const access = payload.access;
-
- const filing = state.filings.find((f) => f.access_number == access);
- const filingDate = new Date(filing.filing_date * 1000);
- const reportDate = new Date(filing.report_date * 1000);
- const filingTime = filingDate.getTime() / 1000;
- const reportTime = reportDate.getTime();
- const filingStr = filingDate.toLocaleDateString();
- const reportStr = reportDate.toLocaleDateString();
- const marketValue = new Intl.NumberFormat().format(filing.market_value);
- const filingStocks = filing.stocks;
-
- const comparisonIndex = state.timeline.comparisons.findIndex(
- (c) => c.type == type
- );
- const comparison = state.timeline.comparisons[comparisonIndex];
-
- state.timeline.comparisons[comparisonIndex] = {
- ...comparison,
- access,
- filing: {
- time: filingTime,
- date: filingStr,
- },
- report: {
- time: reportTime,
- date: reportStr,
- },
- stocks: filingStocks,
- value: marketValue,
- };
- return state;
- },
- setOpen(state) {
- const open = state.timeline.open;
-
- state.timeline.open = !open;
- return state;
- },
- editComparison(state, action) {
- const payload = action.payload;
- const type = payload.type;
-
- const comparison = payload;
- const comparisons = state.timeline.comparisons.map((c) => {
- return c.type == type ? { ...c, ...comparison } : c;
- });
-
- state.timeline.comparisons = comparisons;
- return state;
- },
- editSort(state, action) {
- const payload = action.payload;
- const type = payload.type;
- delete payload.type;
-
- const comparisons = state.timeline.comparisons.map((c) => {
- return c.type == type ? { ...c, sort: { ...c.sort, ...payload } } : c;
- });
-
- state.timeline.comparisons = comparisons;
- return state;
- },
- editDifference(state, action) {
- const payload = action.payload;
- const difference = state.difference;
-
- state.difference = { ...difference, ...payload };
- return state;
- },
-
- [HYDRATE]: (state, action) => {
- return {
- ...state,
- ...action.payload,
- };
- },
- },
-});
-
-export const selectCik = (state) => state.filer.cik;
-export const selectTab = (state) => state.filer.tab;
-export const selectSort = (state) => state.filer.sort;
-export const selectActive = (state) => state.filer.sort.set;
-export const selectSold = (state) => state.filer.sort.sold;
-export const selectNa = (state) => state.filer.sort.na;
-export const selectHeaders = (state) => state.filer.headers;
-export const selectStocks = createSelector(
- [(state) => state.filer.value],
- (stocks) => stocks
-);
-export const selectDates = createSelector(
- [(state) => state.filer.dates],
- (dates) =>
- dates.map((d) => {
- return { ...d, id: d.accessor };
- })
-);
-export const selectPagination = createSelector(
- [(state) => state.filer.sort],
- (sort) => {
- return { limit: sort.pagination, offset: sort.offset, count: sort.count };
- }
-);
-export const selectTimeline = (state) => state.filer.timeline;
-export const selectFilings = (state) => state.filer.filings;
-export const selectPrimary = (state) => state.filer.timeline.comparisons[0];
-export const selectSecondary = (state) => state.filer.timeline.comparisons[1];
-export const selectDifference = (state) => state.filer.difference;
-
-export const {
- setCik,
- setTab,
- activateHeader,
- addHeader,
- editHeader,
- sortHeader,
- sortActive,
- sortSold,
- sortNa,
- updateStocks,
- setStocks,
- sortStocks,
- addDate,
- setHeaders,
- removeHeader,
- removeDate,
- editDate,
- openDate,
- updateDates,
- newDate,
- setPagination,
- setCount,
- setFilingCount,
- setOffset,
- setPrimary,
- setSecondary,
- setFilings,
- editComparison,
- editSort,
- setComparison,
- setOpen,
- editDifference,
-} = filerSlice.actions;
-
-export default filerSlice.reducer;
+import { createSlice, createSelector, PayloadAction } from "@reduxjs/toolkit";
+import { HYDRATE } from "next-redux-wrapper";
+const initialDate: Date = new Date();
+interface Header {
+ display: string;
+ sort: string;
+ accessor: string;
+ active: boolean;
+ tooltip: string;
+}
+const initialHeaders: Header[] = [
+ {
+ display: "Ticker",
+ sort: "ticker",
+ accessor: "ticker_str",
+ active: true,
+ tooltip:
+ "This is a unique series of letters assigned to a security for trading purposes",
+ },
+ {
+ display: "Name",
+ sort: "name",
+ accessor: "name",
+ active: false,
+ tooltip: "The name of the stock.",
+ },
+ {
+ display: "Class",
+ sort: "class",
+ accessor: "class",
+ active: false,
+ tooltip:
+ "This refers to the rights of a stockholder, including things like voting and dividends.",
+ },
+ {
+ display: "Sector",
+ sort: "sector",
+ accessor: "sector",
+ active: false,
+ tooltip: "The broader industry category to which the stock belongs.",
+ },
+ {
+ display: "CUSIP",
+ sort: "cusip",
+ accessor: "cusip",
+ active: false,
+ tooltip:
+ "A unique identifier assigned to each registered security in the United States and Canada.",
+ },
+ {
+ display: "Shares Held",
+ sort: "shares_held",
+ accessor: "shares_held_str",
+ active: false,
+ tooltip: "The number of shares held, or the principal amount of the stock.",
+ },
+ {
+ display: "Market Value",
+ sort: "market_value",
+ accessor: "market_value_str",
+ active: true,
+ tooltip: "The value for the shares of the stock the filer owns.",
+ },
+ {
+ display: "% Portfolio",
+ sort: "portfolio_percent",
+ accessor: "portfolio_str",
+ active: true,
+ tooltip:
+ "The value of this stock's shares divided by the total value of the portfolio, expressed in percent. ( Value of Shares / Value of Portfolio )",
+ },
+ {
+ display: "% Ownership",
+ sort: "ownership_percent",
+ accessor: "ownership_str",
+ active: false,
+ tooltip:
+ "The number of shares owned, divided by the current total of shares outstanding, expressed in percent. This value is only accurate the most value of shares outstanding. ( Shares Owned / Shares Existing )",
+ },
+ {
+ display: "Sold Date",
+ sort: "sold_time",
+ accessor: "sold_str",
+ active: false,
+ tooltip:
+ "The date the stock was sold, taken by retrieving the report date of the last SEC filing said stock showed up on. This is only accurate up to the quarter.",
+ },
+ {
+ display: "Buy Date",
+ sort: "buy_time",
+ accessor: "buy_str",
+ active: false,
+ tooltip:
+ "The date the stock was bought, according to the report date of the first SEC filing it appeared on. This is only accurate up to the quarter, and only the most recent bought date is shown.",
+ },
+ {
+ display: "Price Paid",
+ sort: "buy_price",
+ accessor: "buy_price_str",
+ active: true,
+ tooltip:
+ "The price paid for the stock, estimated by taking a close price most near the quarter from which the stock was first reported.",
+ },
+ {
+ display: "Recent Price",
+ sort: "recent_price",
+ accessor: "recent_price_str",
+ active: true,
+ tooltip:
+ "The recent price of the stock. This may be a couple days delayed.",
+ },
+ {
+ display: "% Gain",
+ sort: "gain_percent",
+ accessor: "gain_str",
+ active: true,
+ tooltip:
+ "The price paid for the stock subtracted from the recent price, and then divided by price paid, expressed in percent. ( ( Recent Price - Price Paid ) / Price Paid )",
+ },
+ {
+ display: "Industry",
+ sort: "industry",
+ accessor: "industry",
+ active: false,
+ tooltip:
+ "The specific sector or category of the economy in which the stock's company operates.",
+ },
+ {
+ display: "Report Date",
+ sort: "report",
+ accessor: "report_str",
+ active: false,
+ tooltip:
+ "The report date listed on the SEC filing this stock was taken from.",
+ },
+];
+const initialComparisons: Header[] = initialHeaders.map((h) => {
+ switch (h.sort) {
+ case "buy_price":
+ return { ...h, active: false };
+ case "recent_price":
+ return { ...h, active: false };
+ case "gain_percent":
+ return { ...h, active: false };
+ default:
+ return h;
+ }
+});
+interface Sort {
+ sort: string;
+ type: string;
+ set: boolean;
+ na: boolean;
+ sold: boolean;
+ reverse: boolean;
+ pagination: number;
+ limit: number;
+ count: number;
+ offset: number;
+}
+const initialSort: Sort = {
+ sort: "ticker",
+ type: "string",
+ set: true,
+ na: false,
+ sold: false,
+ reverse: true,
+ pagination: 100,
+ limit: 100,
+ count: 0,
+ offset: 0,
+};
+interface Filing {
+ time: number;
+ date: string;
+}
+interface Comparison {
+ type: string;
+ access: string;
+ filing: Filing;
+ report: Filing;
+ headers: Header[];
+ sort: Sort;
+ stocks: any[];
+}
+interface Timeline {
+ comparisons: Comparison[];
+ open: boolean;
+}
+interface DateState {
+ year: number;
+ month: number;
+ day: number;
+ timestamp: number;
+ open: boolean;
+ accessor: string;
+}
+interface State {
+ cik: string;
+ value: any[];
+ headers: Header[];
+ tab: string;
+ sort: Sort;
+ filings: any[];
+ timeline: Timeline;
+ difference: {
+ headers: Header[];
+ sort: Sort;
+ stocks: any[];
+ };
+ dates: DateState[];
+}
+const initialState: State = {
+ cik: "",
+ value: [],
+ headers: initialHeaders,
+ tab: "stocks",
+ sort: initialSort,
+ filings: [],
+ timeline: {
+ comparisons: [
+ {
+ type: "primary",
+ access: "",
+ filing: {
+ time: 0,
+ date: "",
+ },
+ report: {
+ time: 0,
+ date: "",
+ },
+ headers: initialComparisons,
+ sort: initialSort,
+ stocks: [],
+ },
+ {
+ type: "secondary",
+ access: "",
+ filing: {
+ time: 0,
+ date: "",
+ },
+ report: {
+ time: 0,
+ date: "",
+ },
+ headers: initialComparisons,
+ sort: initialSort,
+ stocks: [],
+ },
+ ],
+ open: false,
+ },
+ difference: {
+ headers: initialComparisons,
+ sort: initialSort,
+ stocks: [],
+ },
+ dates: [
+ {
+ year: initialDate.getFullYear(),
+ month: initialDate.getMonth(),
+ day: initialDate.getDate(),
+ timestamp: initialDate.getTime() / 1000,
+ open: false,
+ accessor: initialDate.toLocaleDateString(),
+ },
+ ],
+};
+export const filerSlice = createSlice({
+ name: "filer",
+ initialState,
+ reducers: {
+ setCik(state, action: PayloadAction) {
+ Object.keys(initialState).map((k) => {
+ state[k] = initialState[k];
+ });
+ state.cik = action.payload;
+ return state;
+ },
+ setTab(state, action: PayloadAction) {
+ const payload = action.payload;
+ state.tab = payload;
+ return state;
+ },
+ activateHeader(state, action: PayloadAction) {
+ const headers = state.headers;
+ const payload = action.payload;
+ state.headers = headers.map((h) =>
+ h.accessor === payload ? { ...h, active: !h.active } : h
+ );
+ return state;
+ },
+ sortHeader(state, action: PayloadAction<{ sort: string }>) {
+ const payload = action.payload;
+ let type = "string";
+ switch (payload.sort) {
+ case "name":
+ case "sector":
+ case "industry":
+ case "class":
+ case "cusip":
+ type = "string";
+ break;
+ case "shares_held":
+ case "market_value":
+ case "portfolio_percent":
+ case "ownership_percent":
+ case "gain_percent":
+ case "recent_price":
+ case "buy_price":
+ case "report":
+ case "buy":
+ case "sold_time":
+ type = "number";
+ break;
+ case "buy":
+ case "report":
+ case "sold_time":
+ type = "date";
+ break;
+ default:
+ type = typeof payload.sort === "number" ? "number" : "string";
+ break;
+ }
+ state.sort = { ...state.sort, ...payload, type: type };
+ return state;
+ },
+ sortActive(state) {
+ const sort = state.sort;
+ const set = sort.set;
+ state.sort = { ...sort, set: !set };
+ return state;
+ },
+ addHeader(state, action: PayloadAction) {
+ const payload = action.payload;
+ const headers = state.headers;
+ headers.push({
+ ...payload,
+ active: true,
+ });
+ state.headers = headers;
+ return state;
+ },
+ editHeader(state, action: PayloadAction<{ accessor: string; display: string }>) {
+ const payload = action.payload;
+ const headers = state.headers.map((h) =>
+ h.accessor === payload.accessor ? { ...h, display: payload.display } : h
+ );
+ state.headers = headers;
+ return state;
+ },
+ removeHeader(state, action: PayloadAction) {
+ const payload = action.payload;
+ const headers = state.headers.filter((h) => h.accessor !== payload);
+ state.headers = headers;
+ return state;
+ },
+ setHeaders(state, action: PayloadAction) {
+ const payload = action.payload;
+ state.headers = payload;
+ return state;
+ },
+ sortSold(state) {
+ const sort = state.sort;
+ const sold = sort.sold;
+ state.sort = { ...sort, sold: !sold };
+ return state;
+ },
+ sortNa(state) {
+ const sort = state.sort;
+ const na = sort.na;
+ state.sort = { ...sort, na: !na };
+ return state;
+ },
+ setStocks(state, action: PayloadAction) {
+ const stocks = action.payload;
+ state.value = stocks;
+ return state;
+ },
+ updateStocks(state, action: PayloadAction<{ field: string; values: Record }>) {
+ const payload = action.payload;
+ const field = payload.field;
+ const values = payload.values;
+ let stocks = state.value;
+ stocks = stocks.map((stock) => {
+ const cusip = stock.cusip;
+ const value = values[cusip];
+ return { ...stock, [field]: value };
+ });
+ state.value = stocks;
+ return state;
+ },
+ addDate(state, action: PayloadAction) {
+ const dates = state.dates;
+ dates.push(action.payload);
+ state.dates = dates;
+ return state;
+ },
+ removeDate(state, action: PayloadAction) {
+ const accessor = action.payload;
+ const dates = state.dates;
+ state.dates = dates.filter((date) => date.accessor !== accessor);
+ return state;
+ },
+ editDate(state, action: PayloadAction<{ accessor: string; type: string; value: any }>) {
+ const payload = action.payload;
+ const dates = state.dates.map((date) => {
+ if (payload.accessor === date.accessor) {
+ let newDate = new Date(date.year, date.month, date.day);
+ switch (payload.type) {
+ case "year":
+ newDate.setFullYear(payload.value);
+ break;
+ case "month":
+ newDate.setMonth(payload.value);
+ break;
+ case "day":
+ newDate.setDate(payload.value);
+ break;
+ case "date":
+ newDate = new Date(payload.value);
+ break;
+ default:
+ break;
+ }
+ return {
+ year: newDate.getFullYear(),
+ month: newDate.getMonth(),
+ day: newDate.getDate(),
+ timestamp: newDate.getTime() / 1000,
+ open: true,
+ accessor: date.accessor,
+ };
+ } else return date;
+ });
+ state.dates = dates;
+ return state;
+ },
+ updateDates(state, action: PayloadAction) {
+ const payload = action.payload;
+ state.dates = payload;
+ return state;
+ },
+ openDate(state, action: PayloadAction<{ accessor: string; open: boolean }>) {
+ const payload = action.payload;
+ const accessor = payload.accessor;
+ const dates = state.dates.map((date) =>
+ date.accessor === accessor ? { ...date, open: payload.open } : date
+ );
+ state.dates = dates;
+ return state;
+ },
+ newDate(state) {
+ const dates = state.dates;
+ const latestDate = dates.at(-1) || {
+ year: initialDate.getFullYear(),
+ month: initialDate.getMonth(),
+ day: initialDate.getDate(),
+ timestamp: initialDate.getTime() / 1000,
+ open: false,
+ accessor: initialDate.toLocaleDateString(),
+ };
+ const newDate = new Date(latestDate.accessor);
+ newDate.setDate(newDate.getDate() + 1);
+ dates.push({
+ year: newDate.getFullYear(),
+ month: newDate.getMonth(),
+ day: newDate.getDate(),
+ timestamp: newDate.getTime() / 1000,
+ open: false,
+ accessor: newDate.toLocaleDateString(),
+ });
+ state.dates = dates;
+ return state;
+ },
+ setPagination(state, action: PayloadAction) {
+ state.sort.pagination = action.payload;
+ return state;
+ },
+ setCount(state, action: PayloadAction) {
+ const sort = state.sort;
+ const payload = action.payload;
+ const pagination = sort.pagination;
+ if (pagination < 0) {
+ state.sort.pagination = payload > 100 ? 100 : payload;
+ }
+ state.sort.count = payload;
+ return state;
+ },
+ setFilingCount(state, action: PayloadAction<{ type: string; count: number }>) {
+ const payload = action.payload;
+ const type = payload.type;
+ const count = payload.count;
+ const comparisons = state.timeline.comparisons.map((c) =>
+ c.type === type
+ ? {
+ ...c,
+ sort: { ...c.sort, pagination: count > 100 ? 100 : count, count },
+ }
+ : c
+ );
+ state.timeline.comparisons = comparisons;
+ return state;
+ },
+ setOffset(state, action: PayloadAction) {
+ const payload = action.payload;
+ if (payload >= 0) {
+ state.sort.offset = Number(payload);
+ }
+ return state;
+ },
+ setFilings(state, action: PayloadAction) {
+ const payload = action.payload;
+ state.filings = payload;
+ return state;
+ },
+ setPrimary(state, action: PayloadAction>) {
+ const payload = action.payload;
+ const comparisons = state.timeline.comparisons;
+ const primary = comparisons[0];
+ state.timeline.comparisons[0] = { ...primary, ...payload };
+ return state;
+ },
+ setSecondary(state, action: PayloadAction>) {
+ const payload = action.payload;
+ const comparisons = state.timeline.comparisons;
+ const secondary = comparisons[1];
+ state.timeline.comparisons[1] = { ...secondary, ...payload };
+ return state;
+ },
+ setComparison(state, action: PayloadAction<{ type: string; access: string }>) {
+ const payload = action.payload;
+ const type = payload.type;
+ const access = payload.access;
+ const filing = state.filings.find((f) => f.access_number == access);
+ const filingDate = new Date(filing.filing_date * 1000);
+ const reportDate = new Date(filing.report_date * 1000);
+ const filingTime = filingDate.getTime() / 1000;
+ const reportTime = reportDate.getTime();
+ const filingStr = filingDate.toLocaleDateString();
+ const reportStr = reportDate.toLocaleDateString();
+ const marketValue = new Intl.NumberFormat().format(filing.market_value);
+ const filingStocks = filing.stocks;
+ const comparisonIndex = state.timeline.comparisons.findIndex(
+ (c) => c.type == type
+ );
+ const comparison = state.timeline.comparisons[comparisonIndex];
+ state.timeline.comparisons[comparisonIndex] = {
+ ...comparison,
+ access,
+ filing: {
+ time: filingTime,
+ date: filingStr,
+ },
+ report: {
+ time: reportTime,
+ date: reportStr,
+ },
+ stocks: filingStocks,
+ value: marketValue,
+ };
+ return state;
+ },
+ setOpen(state) {
+ const open = state.timeline.open;
+ state.timeline.open = !open;
+ return state;
+ },
+ editComparison(state, action: PayloadAction>) {
+ const payload = action.payload;
+ const type = payload.type;
+ const comparison = payload;
+ const comparisons = state.timeline.comparisons.map((c) => {
+ return c.type == type ? { ...c, ...comparison } : c;
+ });
+ state.timeline.comparisons = comparisons;
+ return state;
+ },
+ editSort(state, action: PayloadAction<{ type: string; [key: string]: any }>) {
+ const payload = action.payload;
+ const type = payload.type;
+ delete payload.type;
+ const comparisons = state.timeline.comparisons.map((c) => {
+ return c.type == type ? { ...c, sort: { ...c.sort, ...payload } } : c;
+ });
+ state.timeline.comparisons = comparisons;
+ return state;
+ },
+ editDifference(state, action: PayloadAction>) {
+ const payload = action.payload;
+ const difference = state.difference;
+ state.difference = { ...difference, ...payload };
+ return state;
+ },
+ [HYDRATE]: (state, action: PayloadAction) => {
+ return {
+ ...state,
+ ...action.payload,
+ };
+ },
+ },
+});
+export const selectCik = (state: { filer: State }) => state.filer.cik;
+export const selectTab = (state: { filer: State }) => state.filer.tab;
+export const selectSort = (state: { filer: State }) => state.filer.sort;
+export const selectActive = (state: { filer: State }) => state.filer.sort.set;
+export const selectSold = (state: { filer: State }) => state.filer.sort.sold;
+export const selectNa = (state: { filer: State }) => state.filer.sort.na;
+export const selectHeaders = (state: { filer: State }) => state.filer.headers;
+export const selectStocks = createSelector(
+ [(state: { filer: State }) => state.filer.value],
+ (stocks) => stocks
+);
+export const selectDates = createSelector(
+ [(state: { filer: State }) => state.filer.dates],
+ (dates) =>
+ dates.map((d) => {
+ return { ...d, id: d.accessor };
+ })
+);
+export const selectPagination = createSelector(
+ [(state: { filer: State }) => state.filer.sort],
+ (sort) => {
+ return { limit: sort.pagination, offset: sort.offset, count: sort.count };
+ }
+);
+export const selectTimeline = (state: { filer: State }) => state.filer.timeline;
+export const selectFilings = (state: { filer: State }) => state.filer.filings;
+export const selectPrimary = (state: { filer: State }) => state.filer.timeline.comparisons[0];
+export const selectSecondary = (state: { filer: State }) => state.filer.timeline.comparisons[1];
+export const selectDifference = (state: { filer: State }) => state.filer.difference;
+export const {
+ setCik,
+ setTab,
+ activateHeader,
+ addHeader,
+ editHeader,
+ sortHeader,
+ sortActive,
+ sortSold,
+ sortNa,
+ updateStocks,
+ setStocks,
+ addDate,
+ setHeaders,
+ removeHeader,
+ removeDate,
+ editDate,
+ openDate,
+ updateDates,
+ newDate,
+ setPagination,
+ setCount,
+ setFilingCount,
+ setOffset,
+ setPrimary,
+ setSecondary,
+ setFilings,
+ editComparison,
+ editSort,
+ setComparison,
+ setOpen,
+ editDifference,
+} = filerSlice.actions;
+export default filerSlice.reducer;
\ No newline at end of file
diff --git a/frontend/redux/store.js b/frontend/redux/store.js
deleted file mode 100644
index d6f57e3b..00000000
--- a/frontend/redux/store.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { configureStore } from "@reduxjs/toolkit";
-import { createWrapper } from "next-redux-wrapper";
-
-import { filerSlice } from "./filerSlice";
-
-const makeStore = () =>
- configureStore({
- reducer: {
- [filerSlice.name]: filerSlice.reducer,
- },
- middleware: (getDefaultMiddleware) =>
- getDefaultMiddleware({
- serializableCheck: false,
- }),
- devTools: true,
- });
-
-export const wrapper = createWrapper(makeStore);
diff --git a/frontend/redux/store.ts b/frontend/redux/store.ts
new file mode 100644
index 00000000..036f028d
--- /dev/null
+++ b/frontend/redux/store.ts
@@ -0,0 +1,19 @@
+import { configureStore, EnhancedStore } from "@reduxjs/toolkit";
+import { createWrapper, Context, MakeStore } from "next-redux-wrapper";
+import { AnyAction, MiddlewareArray } from "@reduxjs/toolkit";
+import { filerSlice } from "./filerSlice";
+interface StoreState {
+ [key: string]: any;
+}
+const makeStore: MakeStore> = (context: Context) =>
+ configureStore({
+ reducer: {
+ [filerSlice.name]: filerSlice.reducer,
+ },
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware({
+ serializableCheck: false,
+ }) as MiddlewareArray,
+ devTools: true,
+ });
+export const wrapper = createWrapper>(makeStore);
\ No newline at end of file
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 00000000..f9cfebeb
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "incremental": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./*"],
+ "@/images/*": ["public/static/*"],
+ "@fonts": ["components/fonts"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}