Merge branch 'master' into 'production'

Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!121
This commit is contained in:
Ilari Ojakorpi
2023-03-05 17:23:56 +00:00
38 changed files with 1869 additions and 843 deletions
+3 -1
View File
@@ -16,7 +16,6 @@ const sentryWebpackPluginOptions = {
};
module.exports = withBundleAnalyzer(withSentryConfig({
target: "server",
images: {
domains: [
"api.sahkoinsinoorikilta.fi",
@@ -24,4 +23,7 @@ module.exports = withBundleAnalyzer(withSentryConfig({
"api.dev.sahkoinsinoorikilta.fi",
],
},
sentry: {
hideSourceMaps: true, // Hide source maps, see: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-source-maps
}
}, sentryWebpackPluginOptions));
+1739 -768
View File
File diff suppressed because it is too large Load Diff
+20 -14
View File
@@ -37,9 +37,9 @@
"@types/jest": "^27.4.1",
"@types/js-cookie": "^3.0.1",
"@types/node": "^16.11.36",
"@types/react": "^17.0.19",
"@types/react-csv": "^1.1.2",
"@types/react-dom": "^17.0.9",
"@types/react": "^18.0.15",
"@types/react-csv": "^1.1.3",
"@types/react-dom": "^18.0.6",
"@types/shortid": "^0.0.29",
"@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^5.18.0",
@@ -48,11 +48,11 @@
"eslint": "^8.13.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "^12.1.4",
"eslint-config-next": "^13.1.6",
"eslint-plugin-import": "^2.26.0",
"husky": "^7.0.4",
"jest": "^27.5.1",
"next-sitemap": "^2.5.19",
"next-sitemap": "^3.1.11",
"npm-run-all": "^4.1.5",
"postcss-jsx": "^0.36.4",
"postcss-syntax": "^0.36.2",
@@ -64,31 +64,37 @@
"typescript": "^4.6.3"
},
"dependencies": {
"@next/bundle-analyzer": "^12.1.4",
"@rjsf/core": "^4.1.1",
"@sentry/nextjs": "^6.19.6",
"@next/bundle-analyzer": "^12.2.3",
"@rjsf/core": "^4.2.0",
"@sentry/nextjs": "^7.34.0",
"axios": "^0.26.1",
"date-fns": "^2.28.0",
"fast-deep-equal": "^3.1.3",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"next": "^12.1.4",
"next": "^13.1.6",
"normalize.css": "^8.0.1",
"react": "^17.0.2",
"react": "^18.2.0",
"react-csv": "^2.2.2",
"react-dnd": "15.0.2",
"react-dnd-html5-backend": "15.0.2",
"react-dnd-touch-backend": "15.0.2",
"react-dom": "^17.0.2",
"react-is": "^17.0.2",
"react-markdown": "^8.0.2",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"react-markdown": "^8.0.3",
"react-mde": "^11.5.0",
"react-toastify": "^8.2.0",
"react-toastify": "^9.0.7",
"rehype-raw": "^6.1.1",
"rehype-sanitize": "^5.0.1",
"sharp": "^0.30.3",
"shortid": "^2.2.16",
"styled-components": "^5.3.5",
"swr": "^1.2.2"
},
"overrides": {
"react-mde": {
"react": "$react",
"react-dom": "$react-dom"
}
}
}
+1
View File
@@ -49,6 +49,7 @@ const Panel = styled.div<{ $visible?: boolean }>`
interface AccordionProps {
title: string;
children: React.ReactNode;
}
const Accordion: React.FC<AccordionProps> = ({ title, children }) => {
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
const Icon = "/img/add-icon.png";
+1
View File
@@ -6,6 +6,7 @@ interface ButtonProps {
onClick: () => void;
buttonStyle: "hero" | "filled" | "filter" | "bordered";
selected?: boolean;
children: React.ReactNode;
}
const StyledButton = styled.button<{ $selected?: boolean }>`
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import colors from "@theme/colors";
import Link from "@components/Link";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import colors from "@theme/colors";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image, { ImageProps } from "next/image";
import Image, { ImageProps } from "next/legacy/image";
import styled, { keyframes, Keyframes } from "styled-components";
interface CrossFadeImagesProps {
+1
View File
@@ -6,6 +6,7 @@ interface DropDownBoxProps {
onMouseEnter: () => void;
onMouseLeave: () => void;
visible: boolean;
children: React.ReactNode;
}
const Box = styled.div`
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import { Link } from "@components/index";
+5 -1
View File
@@ -23,7 +23,11 @@ const Container = styled.div`
}
`;
const Hero: React.FC = ({ children }) => (
type HeroProps = {
children: React.ReactNode;
};
const Hero: React.FC<HeroProps> = ({ children }) => (
<Container>
{children}
</Container>
+1
View File
@@ -35,6 +35,7 @@ type Colors = "darkBlue" | "lightTurquoise";
interface HeroAsideProps {
bgColor: Colors;
children: React.ReactNode;
}
// TODO: Color combos
@@ -6,6 +6,7 @@ import breakpoints from "@theme/breakpoints";
interface HeroPrimarySectionProps {
header: string;
text?: string;
children?: React.ReactNode;
}
const Section = styled.section`
@@ -22,6 +22,7 @@ const Item = styled.div`
interface HeroSecondarySectionItemProps {
note?: string;
children: React.ReactNode;
}
export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({ note, children }) => (
@@ -52,6 +53,7 @@ const Items = styled.div`
interface HeroSecondarySectionProps {
heading: string;
children: React.ReactNode;
}
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({ heading, children }) => (
+5 -1
View File
@@ -6,7 +6,11 @@ const Box = styled.div`
text-align: center;
`;
const InfoBox: React.FC = ({ children }) => (
type InfoBoxProps = {
children?: React.ReactNode
};
const InfoBox: React.FC<InfoBoxProps> = ({ children }) => (
<Box>
{children}
</Box>
+18 -8
View File
@@ -2,6 +2,7 @@ import React from "react";
import NextJSLink, { LinkProps } from "next/link";
interface Props extends Omit<LinkProps, "href" | "as"> {
children?: React.ReactNode;
to: string;
template?: string;
target?: string;
@@ -15,18 +16,27 @@ const Link: React.FC<Props> = ({
}) => {
if (template) {
return (
<NextJSLink href={template} passHref={passHref} as={to} {...props}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
</NextJSLink>
<NextJSLink
href={template}
passHref={passHref}
as={to}
{...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
}
if (to.startsWith("/") || to.startsWith("#")) {
return (
<NextJSLink href={to} passHref={passHref} {...props}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
</NextJSLink>
<NextJSLink
href={to}
passHref={passHref}
{...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
}
+1
View File
@@ -6,6 +6,7 @@ import { Link } from "@components/index";
interface NavbarChildLinkProps {
to: string;
children: React.ReactNode;
}
const StyledLink = styled(Link)`
+1
View File
@@ -38,6 +38,7 @@ interface NavbarDropdownLinkProps {
to: string;
text: string;
exploded?: boolean; // if exploded, show items directly underneath without a dropdown menu
children?: React.ReactNode;
}
const NavbarDropdownLink: React.FC<NavbarDropdownLinkProps> = ({
+1
View File
@@ -6,6 +6,7 @@ import Link from "@components/Link";
interface PageLinkProps {
to: string;
desc: string;
children: React.ReactNode;
}
const StyledPageLink = styled.div`
+6 -4
View File
@@ -1,5 +1,5 @@
import React, {
createContext, useContext, useReducer,
createContext, useContext, useMemo, useReducer,
} from "react";
import fi from "./locales/fi/common.json";
import en from "./locales/en/common.json";
@@ -67,8 +67,7 @@ const Reducer = (state: Store, action: Lang) => {
};
const LocaleContext = createContext(initialState);
const LocaleStore: React.FC = ({ children }) => {
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const changeLanguage = (action: Lang) => {
dispatch(action);
@@ -78,8 +77,11 @@ const LocaleStore: React.FC = ({ children }) => {
// Just ignore if fails to store value in user's browser
}
};
const localeValue = useMemo(() => ({ ...state, changeLanguage }), [state]);
return (
<LocaleContext.Provider value={{ ...state, changeLanguage }}>
<LocaleContext.Provider value={localeValue}>
{children}
</LocaleContext.Provider>
);
+7 -12
View File
@@ -1,12 +1,12 @@
import React from "react";
import Document, {
Html, Head, Main, NextScript, DocumentContext, DocumentInitialProps,
Html, Head, Main, NextScript, DocumentContext,
} from "next/document";
import { ServerStyleSheet } from "styled-components";
import Favicons from "@components/Favicons";
export default class MyDocument extends Document<{ styleTags: unknown }> {
static getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
@@ -16,20 +16,15 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
styles: [initialProps.styles, sheet.getStyleElement()],
};
} finally {
sheet.seal();
}
};
}
render(): JSX.Element {
const { styleTags } = this.props;
const { styles } = this.props;
return (
<Html lang="fi">
<Head>
@@ -37,7 +32,7 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
<Favicons />
</Head>
<body>
{styleTags}
{styles}
<Main />
<NextScript />
</body>
+3 -3
View File
@@ -109,6 +109,7 @@ const TitleContainer = styled.div`
const CommitteeContainer: React.FC<{
committee: Committee;
children: React.ReactNode;
}> = ({ committee, children }) => (
<Container>
<TitleContainer>
@@ -185,14 +186,14 @@ const ContactsPageView: React.FC = () => (
<BlueLink to="mailto:hallitus@sahkoinsinoorikilta.fi">
hallitus@sahkoinsinoorikilta.fi
</BlueLink>
{". Hallituksen yksittäisiin jäseniin saat yhteyden etunimi.sukunimi@sahkoinsinoorikilta.fi osoitteista."}
. Hallituksen yksittäisiin jäseniin saat yhteyden etunimi.sukunimi@sahkoinsinoorikilta.fi osoitteista.
</p>
<p>
{"Hallitukselle voi myös lähettää palautetta täyttämällä "}
<BlueLink to="https://docs.google.com/forms/d/e/1FAIpQLSeD8Hm66uvwr7Xa2WGgOCfI2RS1NrZsmISf2QBKUcJf_stv8g/viewform?usp=sf_link">
palautelomakkeen
</BlueLink>
{", lomakkeen vastauksia käydään läpi hallituksen kokouksissa."}
, lomakkeen vastauksia käydään läpi hallituksen kokouksissa.
</p>
</div>
)}
@@ -204,5 +205,4 @@ const ContactsPageView: React.FC = () => (
</>
);
export default ContactsPageView;
+13 -13
View File
@@ -11,7 +11,7 @@
"name": "Otto Julkunen",
"phone_number": null,
"email": "otto.julkunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/ottom.jpg"
}
]
},
@@ -23,7 +23,7 @@
"name": "Karoliina Talvikangas",
"phone_number": null,
"email": "karoliina.talvikangas@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/karoliina.jpg"
}
]
},
@@ -35,7 +35,7 @@
"name": "Ville Lairila",
"phone_number": null,
"email": "ville.lairila@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/ville.jpg"
}
]
},
@@ -47,7 +47,7 @@
"name": "Aaron Löfgren",
"phone_number": null,
"email": "aaron.lofgren@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/aaron.jpg"
}
]
},
@@ -59,7 +59,7 @@
"name": "Kasper Skog",
"phone_number": null,
"email": "kasper.skog@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/kasper.jpg"
}
]
},
@@ -71,7 +71,7 @@
"name": "Roni Vallius",
"phone_number": null,
"email": "roni.vallius@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/roni.jpg"
}
]
},
@@ -83,7 +83,7 @@
"name": "Elina Huttunen",
"phone_number": null,
"email": "elina.huttunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/elina.jpg"
}
]
},
@@ -95,7 +95,7 @@
"name": "Julia Pykälä-aho",
"phone_number": null,
"email": "julia.pykalaaho@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/julia.jpg"
}
]
},
@@ -107,7 +107,7 @@
"name": "Juulia Härkönen",
"phone_number": null,
"email": "juulia.harkonen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/juulia.jpg"
}
]
},
@@ -119,7 +119,7 @@
"name": "Tommi Sytelä",
"phone_number": null,
"email": "tommi.sytela@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/tommi.jpg"
}
]
},
@@ -131,7 +131,7 @@
"name": "Pyry Vaara",
"phone_number": null,
"email": "pyry.vaara@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/pyry.jpg"
}
]
},
@@ -143,7 +143,7 @@
"name": "Nette Levijoki",
"phone_number": null,
"email": "nette.levijoki@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/nette.jpg"
}
]
},
@@ -155,7 +155,7 @@
"name": "Visa Kurvi",
"phone_number": null,
"email": "visa.kurvi@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"image": "https://static.sahkoinsinoorikilta.fi/img/board/visa.jpg"
}
]
}
@@ -9,7 +9,7 @@ import JobAdList from "./JobAdList";
import BoardJson from "../ContactsPage/board.json";
const EXCURSION_RULES = "https://static.sahkoinsinoorikilta.fi/saannot/excursiosaannot.pdf";
const CORPORATE_MASTER_INFO = BoardJson.roles.filter(role => { return role.name_fi === "Yrityssuhdemestari"})[0].representatives[0];
const CORPORATE_MASTER_INFO = BoardJson.roles.filter((role) => role.name_fi === "Yrityssuhdemestari")[0].representatives[0];
interface CorporatePageViewProps {
jobAds: JobAd[];
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
+3 -3
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import {
CTASection, TextSection, InfoBox, PageLink, Link,
@@ -147,10 +147,10 @@ const FreshmenPageView: React.FC = () => (
<h3 id="isot">Isoryhmät</h3>
<div>
<p>
SIK:n fuksit nauttivat hurmaavien ISOhenkilöidensä opastuksesta ja hellästä huolenpidosta omissa fuksiryhmissään.
SIK:n fuksit nauttivat hurmaavien ISOhenkilöidensä opastuksesta ja hellästä huolenpidosta omissa fuksiryhmissään.
</p>
<p>
ISOt ovat hiukan vanhempia opiskelijoita ja kiltalaisia, joiden tehtävänä on olla tukenasi fuksivuoden ajan. Ensimmäisenä päivänä teidät jaetaan noin kymmenen hengen fuksiryhmiin ja jokaiseen ryhmään kuuluu kolmesta viiteen ISOa, joista yksi toimii opintoISOna. ISOilta voit kysyä mitä vain opiskeluun ja opiskelijaelämään liittyen. Vaikka he eivät tietäisi vastausta, he luultavimmin osaavat auttaa sinua vastausten löytämisessä.
ISOt ovat hiukan vanhempia opiskelijoita ja kiltalaisia, joiden tehtävänä on olla tukenasi fuksivuoden ajan. Ensimmäisenä päivänä teidät jaetaan noin kymmenen hengen fuksiryhmiin ja jokaiseen ryhmään kuuluu kolmesta viiteen ISOa, joista yksi toimii opintoISOna. ISOilta voit kysyä mitä vain opiskeluun ja opiskelijaelämään liittyen. Vaikka he eivät tietäisi vastausta, he luultavimmin osaavat auttaa sinua vastausten löytämisessä.
</p>
<p>
Kuten sanottu ISOt tukevat sinua koko fuksivuoden ajan, mutta eniten tulet näkemään heitä Orientaatioviikolla, jolloin he kulkevat fuksiryhmäsi kanssa ympäri Otaniemeä ja avaavat ovia teekkariuden saloihin. He auttavat sinua myös löytämään opintojen aloittamiseen tarvittavat asiat ja tukevat esimerkiksi lukujärjestyksen tekemisessä ja kirjastokortin, sekä matkakortin ja opiskelijakortin hankkimisessa.
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import {
Divider,
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import {
CTASection, TextSection, PageLink, Link,
} from "@components/index";
+5 -1
View File
@@ -21,7 +21,11 @@ const Main = styled.div`
}
`;
const AdminListCommon: React.FC = ({ children }) => (
type AdminListCommonProps = {
children: React.ReactNode;
};
const AdminListCommon: React.FC<AdminListCommonProps> = ({ children }) => (
<AdminPageWrapper requiresAuthentication>
<Main>
{children}
+1
View File
@@ -58,6 +58,7 @@ const useShouldRedirect = (enabled = true) => {
type PageProps = {
requiresAuthentication: boolean;
children: React.ReactNode;
};
const AdminPageWrapper: React.FC<PageProps> = ({ requiresAuthentication, children }) => {
+5 -1
View File
@@ -2,7 +2,11 @@ import React from "react";
import Header from "@components/Header";
import Footer from "@components/Footer/Footer";
const PageWrapper: React.FC = ({ children }) => (
type PageWrapperProps = {
children: React.ReactNode;
};
const PageWrapper: React.FC<PageWrapperProps> = ({ children }) => (
<>
<Header />
{children}
+3 -1
View File
@@ -1,6 +1,6 @@
import { Selector } from "testcafe";
import {
getSiteRoot, getPageUrl, generateTestForm, deleteEvent, deleteForm, doLogin, generateAccessToken, getPostRequestLogger,
getSiteRoot, getPageUrl, generateTestForm, deleteEvent, deleteForm, doLogin, generateAccessToken, getPostRequestLogger, waitForLogger,
} from "../utils";
const LOGGER = getPostRequestLogger("events/");
@@ -78,6 +78,8 @@ test("Logged in user can create event", async (t) => {
await t.click(submit);
const parsed = JSON.parse(LOGGER.requests[0].response.body as string);
await waitForLogger(LOGGER);
// eslint-disable-next-line no-param-reassign
t.fixtureCtx.eventId = parsed.id;
+3 -1
View File
@@ -1,6 +1,6 @@
import { Selector } from "testcafe";
import {
getSiteRoot, getPageUrl, deleteForm, doLogin, generateAccessToken, getPostRequestLogger,
getSiteRoot, getPageUrl, deleteForm, doLogin, generateAccessToken, getPostRequestLogger, waitForLogger
} from "../utils";
const LOGGER = getPostRequestLogger("signupForm/");
@@ -97,6 +97,8 @@ test("Logged in user can create signup", async (t) => {
await t.click(submit);
const parsed = JSON.parse(LOGGER.requests[0].response.body as string);
await waitForLogger(LOGGER);
// eslint-disable-next-line no-param-reassign
t.fixtureCtx.formId = parsed.id;
+9
View File
@@ -157,3 +157,12 @@ export const generateTestEvent = async (formIds = [], jwt_access: string) => (
);
export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export const waitForLogger = async (logger: RequestLogger) => {
for (let i = 0; i < 50; i++) {
await sleep(100);
if (logger.requests.length > 0 ) {
return;
}
}
}
+1 -1
View File
@@ -59,7 +59,7 @@
"./src/**/*",
"./types/**/*",
"./tests/testcafe/**/*",
"next-sitemap.js",
"next-sitemap.config.js",
"next.config.js",
"jest.config.js",
".eslintrc.js",