Merge branch 'develop'

pull/570/head
sct 4 years ago
commit 16902f85fb

@ -1,8 +1,8 @@
module.exports = { module.exports = {
root: true, root: true,
parser: '@typescript-eslint/parser', // Specifies the ESLint parser parser: '@typescript-eslint/parser', // Specifies the ESLint parser
extends: [ extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
@ -30,6 +30,8 @@ module.exports = {
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'prettier/prettier': ['error', { endOfLine: 'auto' }], 'prettier/prettier': ['error', { endOfLine: 'auto' }],
'formatjs/no-offset': 'error', 'formatjs/no-offset': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
}, },
overrides: [ overrides: [
{ {
@ -52,6 +54,5 @@ module.exports = {
jest: true, jest: true,
es6: true, es6: true,
}, },
reportUnusedDisableDirectives: true, reportUnusedDisableDirectives: true,
}; };

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 sct
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -2,8 +2,19 @@
<img src="https://i.imgur.com/TMoEG7g.png" alt="Overseerr"> <img src="https://i.imgur.com/TMoEG7g.png" alt="Overseerr">
</p> </p>
<p align="center"> <p align="center">
<img src="https://github.com/sct/overseerr/workflows/Overseerr%20Release/badge.svg?branch=master" alt="Overseerr Release" />
<img src="https://github.com/sct/overseerr/workflows/Overseerr%20CI/badge.svg" alt="Overseerr CI"> <img src="https://github.com/sct/overseerr/workflows/Overseerr%20CI/badge.svg" alt="Overseerr CI">
</p> </p>
<p align="center">
<a href="https://discord.gg/ySfaEUcQ">
<img src="https://img.shields.io/discord/783137440809746482" alt="Discord">
</a>
<img src="https://img.shields.io/docker/pulls/sctx/overseerr" alt="Docker pulls">
<a href="https://hosted.weblate.org/engage/overseerr/">
<img src="https://hosted.weblate.org/widgets/overseerr/-/overseerr-frontend/svg-badge.svg" alt="Translation status" />
</a>
<a href="https://lgtm.com/projects/g/sct/overseerr/context:javascript"><img alt="Language grade: JavaScript" src="https://img.shields.io/lgtm/grade/javascript/g/sct/overseerr.svg?logo=lgtm&logoWidth=18"/></a>
</p>
**Overseerr** is a tool for managing requests for your media library. It integrates with existing services such as **Sonarr** and **Radarr**! **Overseerr** is a tool for managing requests for your media library. It integrates with existing services such as **Sonarr** and **Radarr**!
@ -11,25 +22,77 @@
- Full Plex integration. Login and manage user access with Plex! - Full Plex integration. Login and manage user access with Plex!
- Integrates easily with your existing services. Currently Overseerr supports Sonarr and Radarr. More in the future! - Integrates easily with your existing services. Currently Overseerr supports Sonarr and Radarr. More in the future!
- Syncs to your Plex library to know what titles you already have - Syncs to your Plex library to know what titles you already have.
- Complex request system that allows users to request individual seasons or movies in a friendly, easy to use UI - Complex request system that allows users to request individual seasons or movies in a friendly, easy to use UI.
- Incredibly simple request management UI. Don't dig through the app to simply approve recent requests - Incredibly simple request management UI. Don't dig through the app to simply approve recent requests.
- Granular permissions system
- Mobile friendly design, for when you need to approve requests on the go! - Mobile friendly design, for when you need to approve requests on the go!
## In Development ## In Development
- A lot! - User profiles
- User settings page to give users the ability to modify their Overseerr experience to their liking
- Version update notifications in-app
## Planned Features
- More notification types (Slack/Telegram/etc)
- Issues system. This will allow users to report issues with content on your media server.
- Local user system (for those who do not use Plex)
- Compatiblity APIs to work with existing tools in your system
## Running Overseerr
Currently, the only distribution of Overseerr is through Docker images. If you have Docker, you can run Overseerr with the following command:
```
docker run -d \
-e LOG_LEVEL=info \
-e TZ=Asia/Tokyo \
-p 5055:3000 \
-v /path/to/appdata/config:/config \
--restart unless-stopped \
sctx/overseer
```
After running Overseerr for the first time, visit the web UI at http://[address]:5055 and complete the setup steps to configure Overseerr.
⚠️ Overseerr is currently under very heavy, rapid development and things are likely to break often. We need all the help we can get to find bugs and get them fixed to hit a more stable release. If you would like to help test the bleeding edge, please use the image **sctx/overseerr:develop** instead! ⚠️
## Preview
<img src="https://i.imgur.com/Mjbyruv.png">
## Support
- You can reach us for support on [Discord](https://discord.gg/ySfaEUcQ).
- Bugs can be opened with an issue on [Github](https://github.com/sct/overseerr/issues).
## API Documentation ## API Documentation
- Coming soon Full API documentation will soon be published automatically and available outside of running the app. But currently, you can access the api docs by running Overseerr locally and visiting http://localhost:3000/api-docs
## Contribution ## Contribution
Anyone is welcome to contribute to Docker and pull requests are greatly appreciated! Contributors will be recognized in the future on this very README.
### Developing Overseerr
You can develop Overseer entirely in docker. Make sure you have [Docker Desktop](https://www.docker.com/products/docker-desktop) installed before continuing. You can develop Overseer entirely in docker. Make sure you have [Docker Desktop](https://www.docker.com/products/docker-desktop) installed before continuing.
1. Make sure you have [Docker Desktop](https://www.docker.com/products/docker-desktop) installed. 1. Make sure you have [Docker Desktop](https://www.docker.com/products/docker-desktop) installed.
2. Run `docker-compose up -d` to start the server 2. Run `docker-compose up -d` to start the server.
3. Access the container at http://localhost:3000 3. Access the container at http://localhost:3000
That's it! If Docker isn't your jam, you can always run Overseer with the following yarn commands:
```
yarn
yarn dev
```
You will need NodeJS installed. Once it's built and running, access it locally at http://localhost:3000 just like Docker.
### Translation
We use [Weblate](https://hosted.weblate.org/engage/overseerr/) for our translations so please feel free to contribute to localizing Overseerr!

@ -14,6 +14,7 @@
"migration:run": "ts-node --project server/tsconfig.json ./node_modules/.bin/typeorm migration:run", "migration:run": "ts-node --project server/tsconfig.json ./node_modules/.bin/typeorm migration:run",
"format": "prettier --write ." "format": "prettier --write ."
}, },
"license": "MIT",
"dependencies": { "dependencies": {
"@svgr/webpack": "^5.4.0", "@svgr/webpack": "^5.4.0",
"axios": "^0.20.0", "axios": "^0.20.0",
@ -36,6 +37,7 @@
"pug": "^3.0.0", "pug": "^3.0.0",
"react": "16.13.1", "react": "16.13.1",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-intersection-observer": "^8.31.0",
"react-intl": "^5.8.5", "react-intl": "^5.8.5",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-toast-notifications": "^2.4.0", "react-toast-notifications": "^2.4.0",
@ -46,14 +48,14 @@
"swagger-ui-express": "^4.1.4", "swagger-ui-express": "^4.1.4",
"swr": "^0.3.5", "swr": "^0.3.5",
"typeorm": "^0.2.26", "typeorm": "^0.2.26",
"uuid": "^8.3.0", "uuid": "^8.3.1",
"winston": "^3.3.3", "winston": "^3.3.3",
"xml2js": "^0.4.23", "xml2js": "^0.4.23",
"yamljs": "^0.3.0", "yamljs": "^0.3.0",
"yup": "^0.29.3" "yup": "^0.29.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.11.6", "@babel/cli": "^7.12.8",
"@commitlint/cli": "^11.0.0", "@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0", "@commitlint/config-conventional": "^11.0.0",
"@semantic-release/changelog": "^5.0.1", "@semantic-release/changelog": "^5.0.1",
@ -68,38 +70,38 @@
"@types/email-templates": "^7.1.0", "@types/email-templates": "^7.1.0",
"@types/express": "^4.17.8", "@types/express": "^4.17.8",
"@types/express-session": "^1.17.0", "@types/express-session": "^1.17.0",
"@types/lodash": "^4.14.161", "@types/lodash": "^4.14.165",
"@types/node": "^14.10.0", "@types/node": "^14.14.10",
"@types/node-schedule": "^1.3.1", "@types/node-schedule": "^1.3.1",
"@types/nodemailer": "^6.4.0", "@types/nodemailer": "^6.4.0",
"@types/react": "^16.9.49", "@types/react": "^17.0.0",
"@types/react-dom": "^16.9.8", "@types/react-dom": "^17.0.0",
"@types/react-toast-notifications": "^2.4.0", "@types/react-toast-notifications": "^2.4.0",
"@types/react-transition-group": "^4.4.0", "@types/react-transition-group": "^4.4.0",
"@types/swagger-ui-express": "^4.1.2", "@types/swagger-ui-express": "^4.1.2",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@types/xml2js": "^0.4.5", "@types/xml2js": "^0.4.7",
"@types/yamljs": "^0.2.31", "@types/yamljs": "^0.2.31",
"@types/yup": "^0.29.9", "@types/yup": "^0.29.10",
"@typescript-eslint/eslint-plugin": "^4.0.0", "@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^3.10.1", "@typescript-eslint/parser": "^4.9.1",
"autoprefixer": "^9", "autoprefixer": "^9",
"babel-plugin-react-intl": "^8.2.2", "babel-plugin-react-intl": "^8.2.2",
"babel-plugin-react-intl-auto": "^3.3.0", "babel-plugin-react-intl-auto": "^3.3.0",
"commitizen": "^4.2.1", "commitizen": "^4.2.1",
"copyfiles": "^2.4.0", "copyfiles": "^2.4.0",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"eslint": "^7.10.0", "eslint": "^7.15.0",
"eslint-config-prettier": "^6.11.0", "eslint-config-prettier": "^7.0.0",
"eslint-plugin-formatjs": "^2.7.10", "eslint-plugin-formatjs": "^2.9.10",
"eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.2.0",
"eslint-plugin-react": "^7.20.6", "eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.1.2", "eslint-plugin-react-hooks": "^4.2.0",
"extract-react-intl-messages": "^4.1.1", "extract-react-intl-messages": "^4.1.1",
"husky": "^4.3.0", "husky": "^4.3.5",
"lint-staged": "^10.4.0", "lint-staged": "^10.5.3",
"nodemon": "^2.0.4", "nodemon": "^2.0.6",
"postcss": "^7", "postcss": "^7",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
"prettier": "^2.1.2", "prettier": "^2.1.2",

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

@ -679,9 +679,10 @@ class TheMovieDb {
public getMovieTrending = async ({ public getMovieTrending = async ({
page = 1, page = 1,
timeWindow = 'day', timeWindow = 'day',
}: { page?: number; timeWindow?: 'day' | 'week' } = {}): Promise< }: {
TmdbSearchMovieResponse page?: number;
> => { timeWindow?: 'day' | 'week';
} = {}): Promise<TmdbSearchMovieResponse> => {
try { try {
const response = await this.axios.get<TmdbSearchMovieResponse>( const response = await this.axios.get<TmdbSearchMovieResponse>(
`/trending/movie/${timeWindow}`, `/trending/movie/${timeWindow}`,
@ -701,9 +702,10 @@ class TheMovieDb {
public getTvTrending = async ({ public getTvTrending = async ({
page = 1, page = 1,
timeWindow = 'day', timeWindow = 'day',
}: { page?: number; timeWindow?: 'day' | 'week' } = {}): Promise< }: {
TmdbSearchTvResponse page?: number;
> => { timeWindow?: 'day' | 'week';
} = {}): Promise<TmdbSearchTvResponse> => {
try { try {
const response = await this.axios.get<TmdbSearchTvResponse>( const response = await this.axios.get<TmdbSearchTvResponse>(
`/trending/tv/${timeWindow}`, `/trending/tv/${timeWindow}`,

@ -100,7 +100,7 @@ class Media {
} }
@AfterUpdate() @AfterUpdate()
private async notifyAvailable() { private async _notifyAvailable() {
if (this.status === MediaStatus.AVAILABLE) { if (this.status === MediaStatus.AVAILABLE) {
if (this.mediaType === MediaType.MOVIE) { if (this.mediaType === MediaType.MOVIE) {
const requestRepository = getRepository(MediaRequest); const requestRepository = getRepository(MediaRequest);

@ -62,7 +62,7 @@ export class MediaRequest {
} }
@AfterInsert() @AfterInsert()
private async notifyNewRequest() { private async _notifyNewRequest() {
if (this.status === MediaRequestStatus.PENDING) { if (this.status === MediaRequestStatus.PENDING) {
const mediaRepository = getRepository(Media); const mediaRepository = getRepository(Media);
const media = await mediaRepository.findOne({ const media = await mediaRepository.findOne({
@ -110,7 +110,7 @@ export class MediaRequest {
* auto approved content * auto approved content
*/ */
@AfterUpdate() @AfterUpdate()
private async notifyApproved() { private async _notifyApproved() {
if (this.status === MediaRequestStatus.APPROVED) { if (this.status === MediaRequestStatus.APPROVED) {
const mediaRepository = getRepository(Media); const mediaRepository = getRepository(Media);
const media = await mediaRepository.findOne({ const media = await mediaRepository.findOne({
@ -151,7 +151,7 @@ export class MediaRequest {
@AfterUpdate() @AfterUpdate()
@AfterInsert() @AfterInsert()
private async updateParentStatus() { private async _updateParentStatus() {
const mediaRepository = getRepository(Media); const mediaRepository = getRepository(Media);
const media = await mediaRepository.findOne({ const media = await mediaRepository.findOne({
where: { id: this.media.id }, where: { id: this.media.id },
@ -206,7 +206,7 @@ export class MediaRequest {
} }
@AfterRemove() @AfterRemove()
private async handleRemoveParentUpdate() { private async _handleRemoveParentUpdate() {
const mediaRepository = getRepository(Media); const mediaRepository = getRepository(Media);
const fullMedia = await mediaRepository.findOneOrFail({ const fullMedia = await mediaRepository.findOneOrFail({
where: { id: this.media.id }, where: { id: this.media.id },
@ -219,7 +219,7 @@ export class MediaRequest {
@AfterUpdate() @AfterUpdate()
@AfterInsert() @AfterInsert()
private async sendToRadarr() { private async _sendToRadarr() {
if ( if (
this.status === MediaRequestStatus.APPROVED && this.status === MediaRequestStatus.APPROVED &&
this.type === MediaType.MOVIE this.type === MediaType.MOVIE
@ -267,7 +267,7 @@ export class MediaRequest {
@AfterUpdate() @AfterUpdate()
@AfterInsert() @AfterInsert()
private async sendToSonarr() { private async _sendToSonarr() {
if ( if (
this.status === MediaRequestStatus.APPROVED && this.status === MediaRequestStatus.APPROVED &&
this.type === MediaType.TV this.type === MediaType.TV

@ -8,7 +8,6 @@ import {
AfterInsert, AfterInsert,
AfterUpdate, AfterUpdate,
getRepository, getRepository,
RelationId,
} from 'typeorm'; } from 'typeorm';
import { MediaStatus } from '../constants/media'; import { MediaStatus } from '../constants/media';
import Media from './Media'; import Media from './Media';
@ -42,7 +41,7 @@ class Season {
@AfterInsert() @AfterInsert()
@AfterUpdate() @AfterUpdate()
private async sendSeasonAvailableNotification() { private async _sendSeasonAvailableNotification() {
if (this.status === MediaStatus.AVAILABLE) { if (this.status === MediaStatus.AVAILABLE) {
try { try {
const lazyMedia = await this.media; const lazyMedia = await this.media;

@ -5,7 +5,7 @@ import { createConnection, getRepository } from 'typeorm';
import routes from './routes'; import routes from './routes';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import session from 'express-session'; import session, { Store } from 'express-session';
import { TypeormStore } from 'connect-typeorm/out'; import { TypeormStore } from 'connect-typeorm/out';
import YAML from 'yamljs'; import YAML from 'yamljs';
import swaggerUi from 'swagger-ui-express'; import swaggerUi from 'swagger-ui-express';
@ -29,7 +29,7 @@ app
.then(async () => { .then(async () => {
await createConnection(); await createConnection();
// Load Settings // Load Settings
getSettings().load(); const settings = getSettings().load();
// Register Notification Agents // Register Notification Agents
notificationManager.registerAgents([new DiscordAgent(), new EmailAgent()]); notificationManager.registerAgents([new DiscordAgent(), new EmailAgent()]);
@ -47,7 +47,7 @@ app
server.use( server.use(
'/api', '/api',
session({ session({
secret: 'verysecret', secret: settings.clientId,
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
cookie: { cookie: {
@ -56,7 +56,7 @@ app
store: new TypeormStore({ store: new TypeormStore({
cleanupLimit: 2, cleanupLimit: 2,
ttl: 1000 * 60 * 60 * 24 * 30, ttl: 1000 * 60 * 60 * 24 * 30,
}).connect(sessionRespository), }).connect(sessionRespository) as Store,
}) })
); );
const apiDocs = YAML.load(API_SPEC_PATH); const apiDocs = YAML.load(API_SPEC_PATH);
@ -71,7 +71,7 @@ app
* OpenAPI validator. Otherwise, they are treated as objects instead of strings * OpenAPI validator. Otherwise, they are treated as objects instead of strings
* and response validation will fail * and response validation will fail
*/ */
server.use((req, res, next) => { server.use((_req, res, next) => {
const original = res.json; const original = res.json;
res.json = function jsonp(json) { res.json = function jsonp(json) {
return original.call(this, JSON.parse(JSON.stringify(json))); return original.call(this, JSON.parse(JSON.stringify(json)));
@ -83,8 +83,10 @@ app
server.use( server.use(
( (
err: { status: number; message: string; errors: string[] }, err: { status: number; message: string; errors: string[] },
req: Request, _req: Request,
res: Response, res: Response,
// We must provide a next function for the function signature here even though its not used
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_next: NextFunction _next: NextFunction
) => { ) => {
// format error // format error
@ -96,10 +98,7 @@ app
); );
const port = Number(process.env.PORT) || 3000; const port = Number(process.env.PORT) || 3000;
server.listen(port, (err) => { server.listen(port, () => {
if (err) {
throw err;
}
logger.info(`Server ready on port ${port}`, { logger.info(`Server ready on port ${port}`, {
label: 'SERVER', label: 'SERVER',
}); });

@ -127,7 +127,9 @@ class DiscordAgent implements NotificationAgent {
}; };
} }
public shouldSend(type: Notification): boolean { // TODO: Add checking for type here once we add notification type filters for agents
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public shouldSend(_type: Notification): boolean {
const settings = getSettings(); const settings = getSettings();
if ( if (

@ -7,10 +7,12 @@ import Email from 'email-templates';
import logger from '../../../logger'; import logger from '../../../logger';
import { getRepository } from 'typeorm'; import { getRepository } from 'typeorm';
import { User } from '../../../entity/User'; import { User } from '../../../entity/User';
import { hasPermission, Permission } from '../../permissions'; import { Permission } from '../../permissions';
class EmailAgent implements NotificationAgent { class EmailAgent implements NotificationAgent {
public shouldSend(type: Notification): boolean { // TODO: Add checking for type here once we add notification type filters for agents
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public shouldSend(_type: Notification): boolean {
const settings = getSettings(); const settings = getSettings();
if (settings.notifications.agents.email.enabled) { if (settings.notifications.agents.email.enabled) {

@ -84,7 +84,7 @@ interface NotificationSettings {
} }
interface AllSettings { interface AllSettings {
clientId?: string; clientId: string;
main: MainSettings; main: MainSettings;
plex: PlexSettings; plex: PlexSettings;
radarr: RadarrSettings[]; radarr: RadarrSettings[];
@ -100,8 +100,9 @@ class Settings {
constructor(initialSettings?: AllSettings) { constructor(initialSettings?: AllSettings) {
this.data = { this.data = {
clientId: '',
main: { main: {
apiKey: 'temp', apiKey: '',
applicationUrl: '', applicationUrl: '',
}, },
plex: { plex: {
@ -143,6 +144,10 @@ class Settings {
} }
get main(): MainSettings { get main(): MainSettings {
if (!this.data.main.apiKey) {
this.data.main.apiKey = this.generateApiKey();
this.save();
}
return this.data.main; return this.data.main;
} }
@ -199,6 +204,16 @@ class Settings {
return this.data.clientId; return this.data.clientId;
} }
public regenerateApiKey(): MainSettings {
this.main.apiKey = this.generateApiKey();
this.save();
return this.main;
}
private generateApiKey(): string {
return Buffer.from(`${Date.now()}${this.clientId}`).toString('base64');
}
/** /**
* Settings Load * Settings Load
* *

@ -1,5 +1,4 @@
import { TmdbMovieDetails } from '../api/themoviedb'; import { TmdbMovieDetails } from '../api/themoviedb';
import { MediaRequest } from '../entity/MediaRequest';
import { import {
ProductionCompany, ProductionCompany,
Genre, Genre,

@ -3,7 +3,6 @@ import type {
TmdbPersonResult, TmdbPersonResult,
TmdbTvResult, TmdbTvResult,
} from '../api/themoviedb'; } from '../api/themoviedb';
import type { MediaRequest } from '../entity/MediaRequest';
import Media from '../entity/Media'; import Media from '../entity/Media';
export type MediaType = 'tv' | 'movie' | 'person'; export type MediaType = 'tv' | 'movie' | 'person';

@ -1,9 +1,5 @@
import { Router } from 'express'; import { Router } from 'express';
import TheMovieDb, { import TheMovieDb from '../api/themoviedb';
TmdbMovieResult,
TmdbTvResult,
TmdbPersonResult,
} from '../api/themoviedb';
import { mapMovieResult, mapTvResult, mapPersonResult } from '../models/Search'; import { mapMovieResult, mapTvResult, mapPersonResult } from '../models/Search';
import Media from '../entity/Media'; import Media from '../entity/Media';
import { isMovie, isPerson } from '../utils/typeHelpers'; import { isMovie, isPerson } from '../utils/typeHelpers';

@ -1,7 +1,6 @@
import { Router } from 'express'; import { Router } from 'express';
import TheMovieDb from '../api/themoviedb'; import TheMovieDb from '../api/themoviedb';
import { mapMovieDetails } from '../models/Movie'; import { mapMovieDetails } from '../models/Movie';
import { MediaRequest } from '../entity/MediaRequest';
import { mapMovieResult } from '../models/Search'; import { mapMovieResult } from '../models/Search';
import Media from '../entity/Media'; import Media from '../entity/Media';
import RottenTomatoes from '../api/rottentomatoes'; import RottenTomatoes from '../api/rottentomatoes';

@ -1,5 +1,4 @@
import { Router } from 'express'; import { Router } from 'express';
import next from 'next';
import TheMovieDb from '../api/themoviedb'; import TheMovieDb from '../api/themoviedb';
import logger from '../logger'; import logger from '../logger';
import { import {

@ -4,10 +4,11 @@ import {
RadarrSettings, RadarrSettings,
SonarrSettings, SonarrSettings,
Library, Library,
MainSettings,
} from '../lib/settings'; } from '../lib/settings';
import { getRepository } from 'typeorm'; import { getRepository } from 'typeorm';
import { User } from '../entity/User'; import { User } from '../entity/User';
import PlexAPI, { PlexLibrary } from '../api/plexapi'; import PlexAPI from '../api/plexapi';
import { jobPlexFullSync } from '../job/plexsync'; import { jobPlexFullSync } from '../job/plexsync';
import SonarrAPI from '../api/sonarr'; import SonarrAPI from '../api/sonarr';
import RadarrAPI from '../api/radarr'; import RadarrAPI from '../api/radarr';
@ -19,9 +20,15 @@ import { merge } from 'lodash';
const settingsRoutes = Router(); const settingsRoutes = Router();
settingsRoutes.get('/main', (_req, res) => { settingsRoutes.get('/main', (req, res) => {
const settings = getSettings(); const settings = getSettings();
if (!req.user?.hasPermission(Permission.ADMIN)) {
return res.status(200).json({
applicationUrl: settings.main.applicationUrl,
} as Partial<MainSettings>);
}
res.status(200).json(settings.main); res.status(200).json(settings.main);
}); });
@ -120,7 +127,7 @@ settingsRoutes.get('/plex/sync', (req, res) => {
return res.status(200).json(jobPlexFullSync.status()); return res.status(200).json(jobPlexFullSync.status());
}); });
settingsRoutes.get('/radarr', (req, res) => { settingsRoutes.get('/radarr', (_req, res) => {
const settings = getSettings(); const settings = getSettings();
res.status(200).json(settings.radarr); res.status(200).json(settings.radarr);
@ -261,7 +268,7 @@ settingsRoutes.delete<{ id: string }>('/radarr/:id', (req, res) => {
return res.status(200).json(removed[0]); return res.status(200).json(removed[0]);
}); });
settingsRoutes.get('/sonarr', (req, res) => { settingsRoutes.get('/sonarr', (_req, res) => {
const settings = getSettings(); const settings = getSettings();
res.status(200).json(settings.sonarr); res.status(200).json(settings.sonarr);
@ -372,7 +379,7 @@ settingsRoutes.delete<{ id: string }>('/sonarr/:id', (req, res) => {
return res.status(200).json(removed[0]); return res.status(200).json(removed[0]);
}); });
settingsRoutes.get('/jobs', (req, res) => { settingsRoutes.get('/jobs', (_req, res) => {
return res.status(200).json( return res.status(200).json(
scheduledJobs.map((job) => ({ scheduledJobs.map((job) => ({
name: job.name, name: job.name,
@ -384,7 +391,7 @@ settingsRoutes.get('/jobs', (req, res) => {
settingsRoutes.get( settingsRoutes.get(
'/initialize', '/initialize',
isAuthenticated(Permission.ADMIN), isAuthenticated(Permission.ADMIN),
(req, res) => { (_req, res) => {
const settings = getSettings(); const settings = getSettings();
settings.public.initialized = true; settings.public.initialized = true;
@ -394,7 +401,7 @@ settingsRoutes.get(
} }
); );
settingsRoutes.get('/notifications/discord', (req, res) => { settingsRoutes.get('/notifications/discord', (_req, res) => {
const settings = getSettings(); const settings = getSettings();
res.status(200).json(settings.notifications.agents.discord); res.status(200).json(settings.notifications.agents.discord);
@ -409,7 +416,7 @@ settingsRoutes.post('/notifications/discord', (req, res) => {
res.status(200).json(settings.notifications.agents.discord); res.status(200).json(settings.notifications.agents.discord);
}); });
settingsRoutes.get('/notifications/email', (req, res) => { settingsRoutes.get('/notifications/email', (_req, res) => {
const settings = getSettings(); const settings = getSettings();
res.status(200).json(settings.notifications.agents.email); res.status(200).json(settings.notifications.agents.email);

@ -1,6 +1,5 @@
import { Router } from 'express'; import { Router } from 'express';
import TheMovieDb from '../api/themoviedb'; import TheMovieDb from '../api/themoviedb';
import { MediaRequest } from '../entity/MediaRequest';
import { mapTvDetails, mapSeasonWithEpisodes } from '../models/Tv'; import { mapTvDetails, mapSeasonWithEpisodes } from '../models/Tv';
import { mapTvResult } from '../models/Search'; import { mapTvResult } from '../models/Search';
import Media from '../entity/Media'; import Media from '../entity/Media';

@ -5,7 +5,7 @@ import { hasPermission, Permission } from '../lib/permissions';
const router = Router(); const router = Router();
router.get('/', async (req, res) => { router.get('/', async (_req, res) => {
const userRepository = getRepository(User); const userRepository = getRepository(User);
const users = await userRepository.find(); const users = await userRepository.find();

@ -0,0 +1,85 @@
import React, { AllHTMLAttributes } from 'react';
import { withProperties } from '../../../utils/typeHelpers';
const TBody: React.FC = ({ children }) => {
return (
<tbody className="bg-gray-600 divide-y divide-gray-700">{children}</tbody>
);
};
const TH: React.FC<AllHTMLAttributes<HTMLTableHeaderCellElement>> = ({
children,
className,
...props
}) => {
const style = [
'px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider',
];
if (className) {
style.push(className);
}
return (
<th className={style.join(' ')} {...props}>
{children}
</th>
);
};
interface TDProps extends AllHTMLAttributes<HTMLTableCellElement> {
alignText?: 'left' | 'center' | 'right';
noPadding?: boolean;
}
const TD: React.FC<TDProps> = ({
children,
alignText = 'left',
noPadding,
className,
...props
}) => {
const style = ['whitespace-nowrap text-sm leading-5 text-white'];
switch (alignText) {
case 'left':
style.push('text-left');
break;
case 'center':
style.push('text-center');
break;
case 'right':
style.push('text-right');
break;
}
if (!noPadding) {
style.push('px-6 py-4');
}
if (className) {
style.push(className);
}
return (
<td className={style.join(' ')} {...props}>
{children}
</td>
);
};
const Table: React.FC = ({ children }) => {
return (
<div className="flex flex-col">
<div className="my-2 overflow-x-auto -mx-6 sm:-mx-6 md:mx-4 lg:mx-4">
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div className="shadow overflow-hidden sm:rounded-lg">
<table className="min-w-full">{children}</table>
</div>
</div>
</div>
</div>
);
};
export default withProperties(Table, { TH, TBody, TD });

@ -7,13 +7,11 @@ import type {
} from '../../../server/models/Search'; } from '../../../server/models/Search';
import TitleCard from '../TitleCard'; import TitleCard from '../TitleCard';
import PersonCard from '../PersonCard'; import PersonCard from '../PersonCard';
import { MediaRequest } from '../../../server/entity/MediaRequest';
import TmdbTitleCard from '../TitleCard/TmdbTitleCard'; import TmdbTitleCard from '../TitleCard/TmdbTitleCard';
import Slider from '../Slider'; import Slider from '../Slider';
import Link from 'next/link'; import Link from 'next/link';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { LanguageContext } from '../../context/LanguageContext'; import { LanguageContext } from '../../context/LanguageContext';
import type Media from '../../../server/entity/Media';
import type { MediaResultsResponse } from '../../../server/interfaces/api/mediaInterfaces'; import type { MediaResultsResponse } from '../../../server/interfaces/api/mediaInterfaces';
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces'; import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
import RequestCard from '../RequestCard'; import RequestCard from '../RequestCard';

@ -522,7 +522,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
</div> </div>
<Slider <Slider
sliderKey="cast" sliderKey="cast"
isLoading={!data && !error} isLoading={false}
isEmpty={false} isEmpty={false}
items={data?.credits.cast.slice(0, 20).map((person) => ( items={data?.credits.cast.slice(0, 20).map((person) => (
<PersonCard <PersonCard

@ -1,13 +1,11 @@
import React, { useContext, useState } from 'react'; import React, { useContext } from 'react';
import { useInView } from 'react-intersection-observer';
import type { MediaRequest } from '../../../server/entity/MediaRequest'; import type { MediaRequest } from '../../../server/entity/MediaRequest';
import type { TvDetails } from '../../../server/models/Tv'; import type { TvDetails } from '../../../server/models/Tv';
import type { MovieDetails } from '../../../server/models/Movie'; import type { MovieDetails } from '../../../server/models/Movie';
import useSWR from 'swr'; import useSWR from 'swr';
import { LanguageContext } from '../../context/LanguageContext'; import { LanguageContext } from '../../context/LanguageContext';
import { import { MediaRequestStatus } from '../../../server/constants/media';
MediaStatus,
MediaRequestStatus,
} from '../../../server/constants/media';
import Badge from '../Common/Badge'; import Badge from '../Common/Badge';
import { useUser, Permission } from '../../hooks/useUser'; import { useUser, Permission } from '../../hooks/useUser';
import axios from 'axios'; import axios from 'axios';
@ -16,6 +14,7 @@ import { withProperties } from '../../utils/typeHelpers';
import Link from 'next/link'; import Link from 'next/link';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import globalMessages from '../../i18n/globalMessages'; import globalMessages from '../../i18n/globalMessages';
import StatusBadge from '../StatusBadge';
const messages = defineMessages({ const messages = defineMessages({
requestedby: 'Requested by {username}', requestedby: 'Requested by {username}',
@ -41,6 +40,9 @@ interface RequestCardProps {
} }
const RequestCard: React.FC<RequestCardProps> = ({ request }) => { const RequestCard: React.FC<RequestCardProps> = ({ request }) => {
const { ref, inView } = useInView({
triggerOnce: true,
});
const intl = useIntl(); const intl = useIntl();
const { hasPermission } = useUser(); const { hasPermission } = useUser();
const { locale } = useContext(LanguageContext); const { locale } = useContext(LanguageContext);
@ -49,7 +51,7 @@ const RequestCard: React.FC<RequestCardProps> = ({ request }) => {
? `/api/v1/movie/${request.media.tmdbId}` ? `/api/v1/movie/${request.media.tmdbId}`
: `/api/v1/tv/${request.media.tmdbId}`; : `/api/v1/tv/${request.media.tmdbId}`;
const { data: title, error } = useSWR<MovieDetails | TvDetails>( const { data: title, error } = useSWR<MovieDetails | TvDetails>(
`${url}?language=${locale}` inView ? `${url}?language=${locale}` : null
); );
const { data: requestData, error: requestError, revalidate } = useSWR< const { data: requestData, error: requestError, revalidate } = useSWR<
MediaRequest MediaRequest
@ -66,7 +68,11 @@ const RequestCard: React.FC<RequestCardProps> = ({ request }) => {
}; };
if (!title && !error) { if (!title && !error) {
return <RequestCardPlaceholder />; return (
<div ref={ref}>
<RequestCardPlaceholder />
</div>
);
} }
if (!requestData && !requestError) { if (!requestData && !requestError) {
@ -102,28 +108,11 @@ const RequestCard: React.FC<RequestCardProps> = ({ request }) => {
username: requestData.requestedBy.username, username: requestData.requestedBy.username,
})} })}
</div> </div>
<div className="mt-1 sm:mt-2"> {requestData.media.status && (
{requestData.media.status === MediaStatus.AVAILABLE && ( <div className="mt-1 sm:mt-2">
<Badge badgeType="success"> <StatusBadge status={requestData.media.status} />
{intl.formatMessage(globalMessages.available)} </div>
</Badge> )}
)}
{requestData.media.status === MediaStatus.PARTIALLY_AVAILABLE && (
<Badge badgeType="success">
{intl.formatMessage(globalMessages.partiallyavailable)}
</Badge>
)}
{requestData.media.status === MediaStatus.PROCESSING && (
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.unavailable)}
</Badge>
)}
{requestData.media.status === MediaStatus.PENDING && (
<Badge badgeType="warning">
{intl.formatMessage(globalMessages.pending)}
</Badge>
)}
</div>
{request.seasons.length > 0 && ( {request.seasons.length > 0 && (
<div className="hidden mt-2 text-sm sm:flex items-center"> <div className="hidden mt-2 text-sm sm:flex items-center">
<span className="mr-2">{intl.formatMessage(messages.seasons)}</span> <span className="mr-2">{intl.formatMessage(messages.seasons)}</span>

@ -0,0 +1,250 @@
import React, { useContext } from 'react';
import { useInView } from 'react-intersection-observer';
import type { MediaRequest } from '../../../../server/entity/MediaRequest';
import {
useIntl,
FormattedDate,
FormattedRelativeTime,
defineMessages,
} from 'react-intl';
import { useUser, Permission } from '../../../hooks/useUser';
import { LanguageContext } from '../../../context/LanguageContext';
import type { MovieDetails } from '../../../../server/models/Movie';
import type { TvDetails } from '../../../../server/models/Tv';
import useSWR from 'swr';
import Badge from '../../Common/Badge';
import StatusBadge from '../../StatusBadge';
import Table from '../../Common/Table';
import { MediaRequestStatus } from '../../../../server/constants/media';
import Button from '../../Common/Button';
import axios from 'axios';
import globalMessages from '../../../i18n/globalMessages';
import Link from 'next/link';
const messages = defineMessages({
requestedby: 'Requested by {username}',
seasons: 'Seasons',
notavailable: 'N/A',
});
const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => {
return (movie as MovieDetails).title !== undefined;
};
interface RequestItemProps {
request: MediaRequest;
onDelete: () => void;
}
const RequestItem: React.FC<RequestItemProps> = ({ request, onDelete }) => {
const { ref, inView } = useInView({
triggerOnce: true,
});
const intl = useIntl();
const { hasPermission } = useUser();
const { locale } = useContext(LanguageContext);
const url =
request.type === 'movie'
? `/api/v1/movie/${request.media.tmdbId}`
: `/api/v1/tv/${request.media.tmdbId}`;
const { data: title, error } = useSWR<MovieDetails | TvDetails>(
inView ? `${url}?language=${locale}` : null
);
const { data: requestData, revalidate } = useSWR<MediaRequest>(
`/api/v1/request/${request.id}`,
{
initialData: request,
}
);
const modifyRequest = async (type: 'approve' | 'decline') => {
const response = await axios.get(`/api/v1/request/${request.id}/${type}`);
if (response) {
revalidate();
}
};
const deleteRequest = async () => {
await axios.delete(`/api/v1/request/${request.id}`);
onDelete();
};
if (!title && !error) {
return (
<tr className="w-full bg-gray-800 animate-pulse h-24" ref={ref}>
<td colSpan={6}></td>
</tr>
);
}
if (!title || !requestData) {
return (
<tr className="w-full bg-gray-800 animate-pulse h-24">
<td colSpan={6}></td>
</tr>
);
}
return (
<tr className="w-full bg-gray-800 h-24 p-2 relative text-white">
<Table.TD
noPadding
className="w-20 px-4 relative hidden sm:table-cell align-middle"
>
<Link
href={
request.type === 'movie'
? `/movie/${request.media.tmdbId}`
: `/tv/${request.media.tmdbId}`
}
>
<a>
<img
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`}
alt=""
className="rounded-md shadow-sm cursor-pointer transition transform-gpu duration-300 scale-100 hover:scale-105 hover:shadow-md"
/>
</a>
</Link>
</Table.TD>
<Table.TD>
<Link
href={
requestData.type === 'movie'
? `/movie/${requestData.media.tmdbId}`
: `/tv/${requestData.media.tmdbId}`
}
>
<a className="text-white text-xl mr-2 hover:underline">
{isMovie(title) ? title.title : title.name}
</a>
</Link>
<div className="text-sm">
{intl.formatMessage(messages.requestedby, {
username: requestData.requestedBy.username,
})}
</div>
{requestData.seasons.length > 0 && (
<div className="hidden mt-2 text-sm sm:flex items-center">
<span className="mr-2">{intl.formatMessage(messages.seasons)}</span>
{requestData.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>{season.seasonNumber}</Badge>
</span>
))}
</div>
)}
</Table.TD>
<Table.TD>
<StatusBadge status={requestData.media.status} />
</Table.TD>
<Table.TD>
<div className="flex flex-col">
<span className="text-sm text-gray-300">
<FormattedDate value={requestData.createdAt} />
</span>
</div>
</Table.TD>
<Table.TD>
<div className="flex flex-col">
{requestData.modifiedBy ? (
<span className="text-sm text-gray-300">
{requestData.modifiedBy.username} (
<FormattedRelativeTime
value={Math.floor(
(new Date(requestData.updatedAt).getTime() - Date.now()) /
1000
)}
updateIntervalInSeconds={1}
/>
)
</span>
) : (
<span className="text-sm text-gray-300">N/A</span>
)}
</div>
</Table.TD>
<Table.TD alignText="right">
{requestData.status !== MediaRequestStatus.PENDING &&
hasPermission(Permission.MANAGE_REQUESTS) && (
<Button
buttonType="danger"
buttonSize="sm"
onClick={() => deleteRequest()}
>
<svg
className="w-4 h-4 mr-0 sm:mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
<span className="hidden sm:block">
{intl.formatMessage(globalMessages.delete)}
</span>
</Button>
)}
{requestData.status === MediaRequestStatus.PENDING &&
hasPermission(Permission.MANAGE_REQUESTS) && (
<>
<span className="mr-2">
<Button
buttonType="success"
buttonSize="sm"
onClick={() => modifyRequest('approve')}
>
<svg
className="w-4 h-4 mr-0 sm:mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
<span className="hidden sm:block">
{intl.formatMessage(globalMessages.approve)}
</span>
</Button>
</span>
<span>
<Button
buttonType="danger"
buttonSize="sm"
onClick={() => modifyRequest('decline')}
>
<svg
className="w-4 h-4 mr-0 sm:mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
<span className="hidden sm:block">
{intl.formatMessage(globalMessages.decline)}
</span>
</Button>
</span>
</>
)}
</Table.TD>
</tr>
);
};
export default RequestItem;

@ -0,0 +1,109 @@
import React, { useState } from 'react';
import useSWR from 'swr';
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
import LoadingSpinner from '../Common/LoadingSpinner';
import RequestItem from './RequestItem';
import Header from '../Common/Header';
import Table from '../Common/Table';
import Button from '../Common/Button';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
requests: 'Requests',
mediaInfo: 'Media Info',
status: 'Status',
requestedAt: 'Requested At',
modifiedBy: 'Last Modified By',
showingresults:
'Showing <strong>{from}</strong> to <strong>{to}</strong> of <strong>{total}</strong> results',
next: 'Next',
previous: 'Previous',
});
const RequestList: React.FC = () => {
const intl = useIntl();
const [pageIndex, setPageIndex] = useState(0);
const { data, error, revalidate } = useSWR<RequestResultsResponse>(
`/api/v1/request?take=10&skip=${pageIndex * 10}`
);
if (!data && !error) {
return <LoadingSpinner />;
}
if (!data) {
return <LoadingSpinner />;
}
const hasNextPage = data.pageInfo.pages > pageIndex + 1;
const hasPrevPage = pageIndex > 0;
return (
<>
<Header>{intl.formatMessage(messages.requests)}</Header>
<Table>
<thead>
<Table.TH className="hidden sm:table-cell"></Table.TH>
<Table.TH>{intl.formatMessage(messages.mediaInfo)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.status)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.requestedAt)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.modifiedBy)}</Table.TH>
<Table.TH></Table.TH>
</thead>
<Table.TBody>
{data.results.map((request) => {
return (
<RequestItem
request={request}
key={`request-list-${request.id}`}
onDelete={() => revalidate()}
/>
);
})}
<tr>
<Table.TD colSpan={6} noPadding>
<nav
className="bg-gray-700 px-4 py-3 flex items-center justify-between text-white sm:px-6"
aria-label="Pagination"
>
<div className="hidden sm:block">
<p className="text-sm">
{intl.formatMessage(messages.showingresults, {
from: pageIndex * 10,
to:
data.results.length < 10
? pageIndex * 10 + data.results.length
: pageIndex + 1 * 10,
total: data.pageInfo.results,
strong: function strong(msg) {
return <span className="font-medium">{msg}</span>;
},
})}
</p>
</div>
<div className="flex-1 flex justify-start sm:justify-end">
<span className="mr-2">
<Button
disabled={!hasPrevPage}
onClick={() => setPageIndex((current) => current - 1)}
>
{intl.formatMessage(messages.previous)}
</Button>
</span>
<Button
disabled={!hasNextPage}
onClick={() => setPageIndex((current) => current + 1)}
>
{intl.formatMessage(messages.next)}
</Button>
</div>
</nav>
</Table.TD>
</tr>
</Table.TBody>
</Table>
</>
);
};
export default RequestList;

@ -11,10 +11,10 @@ import {
MediaStatus, MediaStatus,
MediaRequestStatus, MediaRequestStatus,
} from '../../../server/constants/media'; } from '../../../server/constants/media';
import { TvDetails, SeasonWithEpisodes } from '../../../server/models/Tv'; import { TvDetails } from '../../../server/models/Tv';
import type SeasonRequest from '../../../server/entity/SeasonRequest';
import Badge from '../Common/Badge'; import Badge from '../Common/Badge';
import globalMessages from '../../i18n/globalMessages'; import globalMessages from '../../i18n/globalMessages';
import SeasonRequest from '../../../server/entity/SeasonRequest';
const messages = defineMessages({ const messages = defineMessages({
requestadmin: 'Your request will be immediately approved.', requestadmin: 'Your request will be immediately approved.',

@ -1,10 +1,8 @@
import React from 'react'; import React from 'react';
import useSWR from 'swr';
import MovieRequestModal from './MovieRequestModal'; import MovieRequestModal from './MovieRequestModal';
import type { MediaRequest } from '../../../server/entity/MediaRequest';
import type { MediaStatus } from '../../../server/constants/media'; import type { MediaStatus } from '../../../server/constants/media';
import TvRequestModal from './TvRequestModal'; import TvRequestModal from './TvRequestModal';
import { useTransition, animated } from 'react-spring'; import { useTransition } from 'react-spring';
interface RequestModalProps { interface RequestModalProps {
show: boolean; show: boolean;
@ -21,7 +19,6 @@ const RequestModal: React.FC<RequestModalProps> = ({
show, show,
tmdbId, tmdbId,
onComplete, onComplete,
onError,
onUpdating, onUpdating,
onCancel, onCancel,
}) => { }) => {

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React from 'react';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import useSWR from 'swr'; import useSWR from 'swr';
import LoadingSpinner from '../../Common/LoadingSpinner'; import LoadingSpinner from '../../Common/LoadingSpinner';

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React from 'react';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import useSWR from 'swr'; import useSWR from 'swr';
import LoadingSpinner from '../../Common/LoadingSpinner'; import LoadingSpinner from '../../Common/LoadingSpinner';

@ -226,7 +226,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
okText={ okText={
isSubmitting isSubmitting
? intl.formatMessage(messages.saving) ? intl.formatMessage(messages.saving)
: !!radarr : radarr
? intl.formatMessage(messages.save) ? intl.formatMessage(messages.save)
: intl.formatMessage(messages.add) : intl.formatMessage(messages.add)
} }

@ -3,6 +3,7 @@ import useSWR from 'swr';
import LoadingSpinner from '../Common/LoadingSpinner'; import LoadingSpinner from '../Common/LoadingSpinner';
import { FormattedRelativeTime, defineMessages, useIntl } from 'react-intl'; import { FormattedRelativeTime, defineMessages, useIntl } from 'react-intl';
import Button from '../Common/Button'; import Button from '../Common/Button';
import Table from '../Common/Table';
const messages = defineMessages({ const messages = defineMessages({
jobname: 'Job Name', jobname: 'Job Name',
@ -21,55 +22,38 @@ const SettingsJobs: React.FC = () => {
} }
return ( return (
<div className="flex flex-col"> <Table>
<div className="my-2 overflow-x-auto -mx-6 sm:-mx-6 md:mx-4 lg:mx-4"> <thead>
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> <Table.TH>{intl.formatMessage(messages.jobname)}</Table.TH>
<div className="shadow overflow-hidden sm:rounded-lg"> <Table.TH>{intl.formatMessage(messages.nextexecution)}</Table.TH>
<table className="min-w-full"> <Table.TH></Table.TH>
<thead> </thead>
<tr> <Table.TBody>
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> {data?.map((job, index) => (
{intl.formatMessage(messages.jobname)} <tr key={`job-list-${index}`}>
</th> <Table.TD>
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> <div className="text-sm leading-5 text-white">{job.name}</div>
{intl.formatMessage(messages.nextexecution)} </Table.TD>
</th> <Table.TD>
<th className="px-6 py-3 bg-gray-500"></th> <div className="text-sm leading-5 text-white">
</tr> <FormattedRelativeTime
</thead> value={Math.floor(
<tbody className="bg-gray-600 divide-y divide-gray-700"> (new Date(job.nextExecutionTime).getTime() - Date.now()) /
{data?.map((job, index) => ( 1000
<tr key={`job-list-${index}`}> )}
<td className="px-6 py-4 whitespace-nowrap"> updateIntervalInSeconds={1}
<div className="text-sm leading-5 text-white"> />
{job.name} </div>
</div> </Table.TD>
</td> <Table.TD alignText="right">
<td className="px-6 py-4 whitespace-nowrap"> <Button buttonType="primary">
<div className="text-sm leading-5 text-white"> {intl.formatMessage(messages.runnow)}
<FormattedRelativeTime </Button>
value={Math.floor( </Table.TD>
(new Date(job.nextExecutionTime).getTime() - </tr>
Date.now()) / ))}
1000 </Table.TBody>
)} </Table>
updateIntervalInSeconds={1}
/>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm leading-5 font-medium">
<Button buttonType="primary">
{intl.formatMessage(messages.runnow)}
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
); );
}; };

@ -80,7 +80,7 @@ const SettingsLayout: React.FC = ({ children }) => {
<Link href={route}> <Link href={route}>
<a <a
className={`whitespace-nowrap ml-8 first:ml-0 py-4 px-1 border-b-2 border-transparent font-medium text-sm leading-5 ${ className={`whitespace-nowrap ml-8 first:ml-0 py-4 px-1 border-b-2 border-transparent font-medium text-sm leading-5 ${
!!router.pathname.match(regex) ? activeLinkColor : inactiveLinkColor router.pathname.match(regex) ? activeLinkColor : inactiveLinkColor
}`} }`}
aria-current="page" aria-current="page"
> >

@ -7,6 +7,7 @@ import { Form, Formik, Field } from 'formik';
import axios from 'axios'; import axios from 'axios';
import Button from '../Common/Button'; import Button from '../Common/Button';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useUser, Permission } from '../../hooks/useUser';
const messages = defineMessages({ const messages = defineMessages({
generalsettings: 'General Settings', generalsettings: 'General Settings',
@ -19,6 +20,7 @@ const messages = defineMessages({
}); });
const SettingsMain: React.FC = () => { const SettingsMain: React.FC = () => {
const { hasPermission } = useUser();
const intl = useIntl(); const intl = useIntl();
const { data, error, revalidate } = useSWR<MainSettings>( const { data, error, revalidate } = useSWR<MainSettings>(
'/api/v1/settings/main' '/api/v1/settings/main'
@ -41,7 +43,6 @@ const SettingsMain: React.FC = () => {
<div className="mt-6 sm:mt-5"> <div className="mt-6 sm:mt-5">
<Formik <Formik
initialValues={{ initialValues={{
apiKey: data?.apiKey,
applicationUrl: data?.applicationUrl, applicationUrl: data?.applicationUrl,
}} }}
onSubmit={async (values) => { onSubmit={async (values) => {
@ -56,43 +57,45 @@ const SettingsMain: React.FC = () => {
} }
}} }}
> >
{({ errors, touched, isSubmitting }) => { {({ isSubmitting }) => {
return ( return (
<Form> <Form>
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5"> {hasPermission(Permission.ADMIN) && (
<label <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
htmlFor="username" <label
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2" htmlFor="username"
> className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
{intl.formatMessage(messages.apikey)} >
</label> {intl.formatMessage(messages.apikey)}
<div className="mt-1 sm:mt-0 sm:col-span-2"> </label>
<div className="max-w-lg flex rounded-md shadow-sm"> <div className="mt-1 sm:mt-0 sm:col-span-2">
<input <div className="max-w-lg flex rounded-md shadow-sm">
type="text" <input
id="username" type="text"
className="flex-1 form-input block w-full min-w-0 rounded-none rounded-l-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500" id="apiKey"
value={data?.apiKey} className="flex-1 form-input block w-full min-w-0 rounded-none rounded-l-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
readOnly value={data?.apiKey}
/> readOnly
<CopyButton textToCopy={data?.apiKey ?? ''} /> />
<button className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"> <CopyButton textToCopy={data?.apiKey ?? ''} />
<svg <button className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
className="w-5 h-5" <svg
fill="currentColor" className="w-5 h-5"
viewBox="0 0 20 20" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
> xmlns="http://www.w3.org/2000/svg"
<path >
fillRule="evenodd" <path
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" fillRule="evenodd"
clipRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
/> clipRule="evenodd"
</svg> />
</button> </svg>
</button>
</div>
</div> </div>
</div> </div>
</div> )}
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5"> <div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
<label <label
htmlFor="name" htmlFor="name"

@ -49,7 +49,7 @@ const SettingsNotifications: React.FC = ({ children }) => {
<Link href={route}> <Link href={route}>
<a <a
className={`whitespace-nowrap ml-8 first:ml-0 px-3 py-2 font-medium text-sm rounded-md ${ className={`whitespace-nowrap ml-8 first:ml-0 px-3 py-2 font-medium text-sm rounded-md ${
!!router.pathname.match(regex) ? activeLinkColor : inactiveLinkColor router.pathname.match(regex) ? activeLinkColor : inactiveLinkColor
}`} }`}
aria-current="page" aria-current="page"
> >

@ -229,7 +229,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
okText={ okText={
isSubmitting isSubmitting
? intl.formatMessage(messages.saving) ? intl.formatMessage(messages.saving)
: !!sonarr : sonarr
? intl.formatMessage(messages.save) ? intl.formatMessage(messages.save)
: intl.formatMessage(messages.add) : intl.formatMessage(messages.add)
} }

@ -0,0 +1,40 @@
import React from 'react';
import { MediaStatus } from '../../../server/constants/media';
import Badge from '../Common/Badge';
import { useIntl } from 'react-intl';
import globalMessages from '../../i18n/globalMessages';
interface StatusBadgeProps {
status: MediaStatus;
}
const StatusBadge: React.FC<StatusBadgeProps> = ({ status }) => {
const intl = useIntl();
return (
<>
{status === MediaStatus.AVAILABLE && (
<Badge badgeType="success">
{intl.formatMessage(globalMessages.available)}
</Badge>
)}
{status === MediaStatus.PARTIALLY_AVAILABLE && (
<Badge badgeType="success">
{intl.formatMessage(globalMessages.partiallyavailable)}
</Badge>
)}
{status === MediaStatus.PROCESSING && (
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.unavailable)}
</Badge>
)}
{status === MediaStatus.PENDING && (
<Badge badgeType="warning">
{intl.formatMessage(globalMessages.pending)}
</Badge>
)}
</>
);
};
export default StatusBadge;

@ -1,4 +1,5 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useInView } from 'react-intersection-observer';
import useSWR from 'swr'; import useSWR from 'swr';
import type { MovieDetails } from '../../../server/models/Movie'; import type { MovieDetails } from '../../../server/models/Movie';
import type { TvDetails } from '../../../server/models/Tv'; import type { TvDetails } from '../../../server/models/Tv';
@ -15,15 +16,22 @@ const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => {
}; };
const TmdbTitleCard: React.FC<TmdbTitleCardProps> = ({ tmdbId, type }) => { const TmdbTitleCard: React.FC<TmdbTitleCardProps> = ({ tmdbId, type }) => {
const { ref, inView } = useInView({
triggerOnce: true,
});
const { locale } = useContext(LanguageContext); const { locale } = useContext(LanguageContext);
const url = const url =
type === 'movie' ? `/api/v1/movie/${tmdbId}` : `/api/v1/tv/${tmdbId}`; type === 'movie' ? `/api/v1/movie/${tmdbId}` : `/api/v1/tv/${tmdbId}`;
const { data: title, error } = useSWR<MovieDetails | TvDetails>( const { data: title, error } = useSWR<MovieDetails | TvDetails>(
`${url}?language=${locale}` inView ? `${url}?language=${locale}` : null
); );
if (!title && !error) { if (!title && !error) {
return <TitleCard.Placeholder />; return (
<div ref={ref}>
<TitleCard.Placeholder />
</div>
);
} }
if (!title) { if (!title) {

@ -156,12 +156,12 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
/> />
<SlideOver <SlideOver
show={showManager} show={showManager}
title="Manage Series" title={intl.formatMessage(messages.manageModalTitle)}
onClose={() => setShowManager(false)} onClose={() => setShowManager(false)}
subText={data.name} subText={data.name}
> >
<h3 className="text-xl mb-2"> <h3 className="text-xl mb-2">
{intl.formatMessage(messages.manageModalTitle)} {intl.formatMessage(messages.manageModalRequests)}
</h3> </h3>
<div className="bg-gray-600 shadow overflow-hidden rounded-md"> <div className="bg-gray-600 shadow overflow-hidden rounded-md">
<ul> <ul>
@ -473,7 +473,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
</div> </div>
<Slider <Slider
sliderKey="cast" sliderKey="cast"
isLoading={!data && !error} isLoading={false}
isEmpty={false} isEmpty={false}
items={data?.credits.cast.slice(0, 20).map((person) => ( items={data?.credits.cast.slice(0, 20).map((person) => (
<PersonCard <PersonCard

@ -9,6 +9,7 @@ import { hasPermission } from '../../../server/lib/permissions';
import { Permission } from '../../hooks/useUser'; import { Permission } from '../../hooks/useUser';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Header from '../Common/Header'; import Header from '../Common/Header';
import Table from '../Common/Table';
const messages = defineMessages({ const messages = defineMessages({
userlist: 'User List', userlist: 'User List',
@ -37,102 +38,80 @@ const UserList: React.FC = () => {
return ( return (
<> <>
<Header extraMargin={4}>{intl.formatMessage(messages.userlist)}</Header> <Header extraMargin={4}>{intl.formatMessage(messages.userlist)}</Header>
<div className="flex flex-col"> <Table>
<div className="my-2 overflow-x-auto -mx-6 sm:-mx-6 md:mx-4 lg:mx-4"> <thead>
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> <tr>
<div className="shadow overflow-hidden sm:rounded-lg"> <Table.TH>{intl.formatMessage(messages.username)}</Table.TH>
<table className="min-w-full"> <Table.TH>{intl.formatMessage(messages.totalrequests)}</Table.TH>
<thead> <Table.TH>{intl.formatMessage(messages.usertype)}</Table.TH>
<tr> <Table.TH>{intl.formatMessage(messages.role)}</Table.TH>
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> <Table.TH>{intl.formatMessage(messages.created)}</Table.TH>
{intl.formatMessage(messages.username)} <Table.TH>{intl.formatMessage(messages.lastupdated)}</Table.TH>
</th> <Table.TH></Table.TH>
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> </tr>
{intl.formatMessage(messages.totalrequests)} </thead>
</th> <Table.TBody>
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> {data?.map((user) => (
{intl.formatMessage(messages.usertype)} <tr key={`user-list-${user.id}`}>
</th> <Table.TD>
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> <div className="flex items-center">
{intl.formatMessage(messages.role)} <div className="flex-shrink-0 h-10 w-10">
</th> <img
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> className="h-10 w-10 rounded-full"
{intl.formatMessage(messages.created)} src={user.avatar}
</th> alt=""
<th className="px-6 py-3 bg-gray-500 text-left text-xs leading-4 font-medium text-gray-200 uppercase tracking-wider"> />
{intl.formatMessage(messages.lastupdated)} </div>
</th> <div className="ml-4">
<th className="px-6 py-3 bg-gray-500"></th> <div className="text-sm leading-5 font-medium">
</tr> {user.username}
</thead> </div>
<tbody className="bg-gray-600 divide-y divide-gray-700"> <div className="text-sm leading-5 text-gray-300">
{data?.map((user) => ( {user.email}
<tr key={`user-list-${user.id}`}> </div>
<td className="px-6 py-4 whitespace-nowrap"> </div>
<div className="flex items-center"> </div>
<div className="flex-shrink-0 h-10 w-10"> </Table.TD>
<img <Table.TD>
className="h-10 w-10 rounded-full" <div className="text-sm leading-5">{user.requestCount}</div>
src={user.avatar} </Table.TD>
alt="" <Table.TD>
/> <Badge badgeType="warning">
</div> {intl.formatMessage(messages.plexuser)}
<div className="ml-4"> </Badge>
<div className="text-sm leading-5 font-medium text-white"> </Table.TD>
{user.username} <Table.TD>
</div> {hasPermission(Permission.ADMIN, user.permissions)
<div className="text-sm leading-5 text-gray-300"> ? intl.formatMessage(messages.admin)
{user.email} : intl.formatMessage(messages.user)}
</div> </Table.TD>
</div> <Table.TD>
</div> <FormattedDate value={user.createdAt} />
</td> </Table.TD>
<td className="px-6 py-4 whitespace-nowrap"> <Table.TD>
<div className="text-sm leading-5 text-white"> <FormattedDate value={user.updatedAt} />
{user.requestCount} </Table.TD>
</div> <Table.TD alignText="right">
</td> <Button
<td className="px-6 py-4 whitespace-nowrap"> buttonType="warning"
<Badge badgeType="warning"> className="mr-2"
{intl.formatMessage(messages.plexuser)} onClick={() =>
</Badge> router.push(
</td> '/users/[userId]/edit',
<td className="px-6 py-4 whitespace-nowrap text-sm leading-5 text-white"> `/users/${user.id}/edit`
{hasPermission(Permission.ADMIN, user.permissions) )
? intl.formatMessage(messages.admin) }
: intl.formatMessage(messages.user)} >
</td> {intl.formatMessage(messages.edit)}
<td className="px-6 py-4 whitespace-nowrap text-sm leading-5 text-white"> </Button>
<FormattedDate value={user.createdAt} /> <Button buttonType="danger">
</td> {intl.formatMessage(messages.delete)}
<td className="px-6 py-4 whitespace-nowrap text-sm leading-5 text-white"> </Button>
<FormattedDate value={user.updatedAt} /> </Table.TD>
</td> </tr>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm leading-5 font-medium"> ))}
<Button </Table.TBody>
buttonType="warning" </Table>
className="mr-2"
onClick={() =>
router.push(
'/users/[userId]/edit',
`/users/${user.id}/edit`
)
}
>
{intl.formatMessage(messages.edit)}
</Button>
<Button buttonType="danger">
{intl.formatMessage(messages.delete)}
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</> </>
); );
}; };

@ -1,4 +1,4 @@
import React, { useEffect } from 'react'; import React, { useEffect, useRef } from 'react';
import { User, useUser } from '../hooks/useUser'; import { User, useUser } from '../hooks/useUser';
import { useRouter } from 'next/dist/client/router'; import { useRouter } from 'next/dist/client/router';
@ -17,20 +17,19 @@ export const UserContext: React.FC<UserContextProps> = ({
}) => { }) => {
const { user, error, revalidate } = useUser({ initialData: initialUser }); const { user, error, revalidate } = useUser({ initialData: initialUser });
const router = useRouter(); const router = useRouter();
const routing = useRef(false);
useEffect(() => { useEffect(() => {
revalidate(); revalidate();
}, [router.pathname, revalidate]); }, [router.pathname, revalidate]);
useEffect(() => { useEffect(() => {
let routing = false;
if ( if (
!router.pathname.match(/(setup|login)/) && !router.pathname.match(/(setup|login)/) &&
(!user || error) && (!user || error) &&
!routing !routing.current
) { ) {
routing = true; routing.current = true;
location.href = '/login'; location.href = '/login';
} }
}, [router, user, error]); }, [router, user, error]);

@ -1,5 +1,4 @@
import useSwr from 'swr'; import useSwr from 'swr';
import { useRef } from 'react';
import { hasPermission, Permission } from '../../server/lib/permissions'; import { hasPermission, Permission } from '../../server/lib/permissions';
export interface User { export interface User {

@ -13,6 +13,7 @@ const globalMessages = defineMessages({
cancel: 'Cancel', cancel: 'Cancel',
approve: 'Approve', approve: 'Approve',
decline: 'Decline', decline: 'Decline',
delete: 'Delete',
}); });
export default globalMessages; export default globalMessages;

@ -51,6 +51,17 @@
"components.RequestBlock.seasons": "Seasons", "components.RequestBlock.seasons": "Seasons",
"components.RequestCard.requestedby": "Requested by {username}", "components.RequestCard.requestedby": "Requested by {username}",
"components.RequestCard.seasons": "Seasons", "components.RequestCard.seasons": "Seasons",
"components.RequestList.RequestItem.notavailable": "N/A",
"components.RequestList.RequestItem.requestedby": "Requested by {username}",
"components.RequestList.RequestItem.seasons": "Seasons",
"components.RequestList.mediaInfo": "Media Info",
"components.RequestList.modifiedBy": "Last Modified By",
"components.RequestList.next": "Next",
"components.RequestList.previous": "Previous",
"components.RequestList.requestedAt": "Requested At",
"components.RequestList.requests": "Requests",
"components.RequestList.showingresults": "Showing <strong>{from}</strong> to <strong>{to}</strong> of <strong>{total}</strong> results",
"components.RequestList.status": "Status",
"components.RequestModal.cancel": "Cancel Request", "components.RequestModal.cancel": "Cancel Request",
"components.RequestModal.cancelling": "Cancelling...", "components.RequestModal.cancelling": "Cancelling...",
"components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?", "components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?",
@ -275,6 +286,7 @@
"i18n.cancel": "Cancel", "i18n.cancel": "Cancel",
"i18n.decline": "Decline", "i18n.decline": "Decline",
"i18n.declined": "Declined", "i18n.declined": "Declined",
"i18n.delete": "Delete",
"i18n.movies": "Movies", "i18n.movies": "Movies",
"i18n.partiallyavailable": "Partially Available", "i18n.partiallyavailable": "Partially Available",
"i18n.pending": "Pending", "i18n.pending": "Pending",

@ -51,6 +51,17 @@
"components.RequestBlock.seasons": "Saisons", "components.RequestBlock.seasons": "Saisons",
"components.RequestCard.requestedby": "Demandé par {username}", "components.RequestCard.requestedby": "Demandé par {username}",
"components.RequestCard.seasons": "Saisons", "components.RequestCard.seasons": "Saisons",
"components.RequestList.RequestItem.notavailable": "",
"components.RequestList.RequestItem.requestedby": "",
"components.RequestList.RequestItem.seasons": "",
"components.RequestList.mediaInfo": "",
"components.RequestList.modifiedBy": "",
"components.RequestList.next": "",
"components.RequestList.previous": "",
"components.RequestList.requestedAt": "",
"components.RequestList.requests": "",
"components.RequestList.showingresults": "",
"components.RequestList.status": "",
"components.RequestModal.cancel": "Annuler la Demande", "components.RequestModal.cancel": "Annuler la Demande",
"components.RequestModal.cancelling": "Annulation...", "components.RequestModal.cancelling": "Annulation...",
"components.RequestModal.cancelrequest": "Votre demande d'ajout va être annulée. Êtes-vous sûr de vouloir annuler?", "components.RequestModal.cancelrequest": "Votre demande d'ajout va être annulée. Êtes-vous sûr de vouloir annuler?",
@ -275,6 +286,7 @@
"i18n.cancel": "Annuler", "i18n.cancel": "Annuler",
"i18n.decline": "Refuser", "i18n.decline": "Refuser",
"i18n.declined": "Refusé", "i18n.declined": "Refusé",
"i18n.delete": "",
"i18n.movies": "Films", "i18n.movies": "Films",
"i18n.partiallyavailable": "Partiellement Disponible", "i18n.partiallyavailable": "Partiellement Disponible",
"i18n.pending": "En Attente", "i18n.pending": "En Attente",

@ -1,290 +1,302 @@
{ {
"components.Discover.discovermovies": "人気の映画", "components.Discover.discovermovies": "人気の映画",
"components.Discover.discovertv": "人気のテレビ番組", "components.Discover.discovertv": "人気のテレビ番組",
"components.Discover.nopending": "", "components.Discover.nopending": "",
"components.Discover.popularmovies": "人気の映画", "components.Discover.popularmovies": "人気の映画",
"components.Discover.populartv": "人気のテレビ番組", "components.Discover.populartv": "人気のテレビ番組",
"components.Discover.recentlyAdded": "", "components.Discover.recentlyAdded": "",
"components.Discover.recentrequests": "最近のリクエスト", "components.Discover.recentrequests": "最近のリクエスト",
"components.Discover.trending": "", "components.Discover.trending": "",
"components.Discover.upcoming": "", "components.Discover.upcoming": "",
"components.Discover.upcomingmovies": "", "components.Discover.upcomingmovies": "",
"components.Layout.LanguagePicker.changelanguage": "言語", "components.Layout.LanguagePicker.changelanguage": "言語",
"components.Layout.SearchInput.searchPlaceholder": "作品名で検索", "components.Layout.SearchInput.searchPlaceholder": "作品名で検索",
"components.Layout.Sidebar.dashboard": "ホーム", "components.Layout.Sidebar.dashboard": "ホーム",
"components.Layout.Sidebar.requests": "リクエスト", "components.Layout.Sidebar.requests": "リクエスト",
"components.Layout.Sidebar.settings": "設定", "components.Layout.Sidebar.settings": "設定",
"components.Layout.Sidebar.users": "", "components.Layout.Sidebar.users": "",
"components.Layout.UserDropdown.signout": "", "components.Layout.UserDropdown.signout": "",
"components.Layout.alphawarning": "", "components.Layout.alphawarning": "",
"components.Login.signinplex": "", "components.Login.signinplex": "",
"components.MovieDetails.approve": "", "components.MovieDetails.approve": "",
"components.MovieDetails.available": "", "components.MovieDetails.available": "",
"components.MovieDetails.budget": "興行収入", "components.MovieDetails.budget": "興行収入",
"components.MovieDetails.cancelrequest": "チャンセルリクエスト", "components.MovieDetails.cancelrequest": "チャンセルリクエスト",
"components.MovieDetails.cast": "キャスト", "components.MovieDetails.cast": "キャスト",
"components.MovieDetails.decline": "", "components.MovieDetails.decline": "",
"components.MovieDetails.manageModalClearMedia": "", "components.MovieDetails.manageModalClearMedia": "",
"components.MovieDetails.manageModalClearMediaWarning": "", "components.MovieDetails.manageModalClearMediaWarning": "",
"components.MovieDetails.manageModalNoRequests": "", "components.MovieDetails.manageModalNoRequests": "",
"components.MovieDetails.manageModalRequests": "リクエスト", "components.MovieDetails.manageModalRequests": "リクエスト",
"components.MovieDetails.manageModalTitle": "", "components.MovieDetails.manageModalTitle": "",
"components.MovieDetails.originallanguage": "言語", "components.MovieDetails.originallanguage": "言語",
"components.MovieDetails.overview": "ストーリー", "components.MovieDetails.overview": "ストーリー",
"components.MovieDetails.overviewunavailable": "", "components.MovieDetails.overviewunavailable": "",
"components.MovieDetails.pending": "リクエスト中", "components.MovieDetails.pending": "リクエスト中",
"components.MovieDetails.recommendations": "オススメの作品", "components.MovieDetails.recommendations": "オススメの作品",
"components.MovieDetails.recommendationssubtext": "", "components.MovieDetails.recommendationssubtext": "",
"components.MovieDetails.releasedate": "公開日", "components.MovieDetails.releasedate": "公開日",
"components.MovieDetails.request": "リクエストする", "components.MovieDetails.request": "リクエストする",
"components.MovieDetails.revenue": "製作費", "components.MovieDetails.revenue": "製作費",
"components.MovieDetails.runtime": "{minutes}分", "components.MovieDetails.runtime": "{minutes}分",
"components.MovieDetails.similar": "関連作品", "components.MovieDetails.similar": "関連作品",
"components.MovieDetails.similarsubtext": "", "components.MovieDetails.similarsubtext": "",
"components.MovieDetails.status": "状態", "components.MovieDetails.status": "状態",
"components.MovieDetails.unavailable": "", "components.MovieDetails.unavailable": "",
"components.MovieDetails.userrating": "ユーザー評価", "components.MovieDetails.userrating": "ユーザー評価",
"components.MovieDetails.viewrequest": "", "components.MovieDetails.viewrequest": "",
"components.PlexLoginButton.loading": "", "components.PlexLoginButton.loading": "",
"components.PlexLoginButton.loggingin": "", "components.PlexLoginButton.loggingin": "",
"components.PlexLoginButton.loginwithplex": "", "components.PlexLoginButton.loginwithplex": "",
"components.RequestBlock.seasons": "", "components.RequestBlock.seasons": "",
"components.RequestCard.requestedby": "", "components.RequestCard.requestedby": "",
"components.RequestCard.seasons": "", "components.RequestCard.seasons": "",
"components.RequestModal.cancel": "チャンセルリクエスト", "components.RequestList.RequestItem.notavailable": "",
"components.RequestModal.cancelling": "", "components.RequestList.RequestItem.requestedby": "",
"components.RequestModal.cancelrequest": "このリクエストをキャンセルしてよろしいですか?", "components.RequestList.RequestItem.seasons": "",
"components.RequestModal.close": "", "components.RequestList.mediaInfo": "",
"components.RequestModal.extras": "", "components.RequestList.modifiedBy": "",
"components.RequestModal.notrequested": "", "components.RequestList.next": "",
"components.RequestModal.numberofepisodes": "", "components.RequestList.previous": "",
"components.RequestModal.pendingrequest": "", "components.RequestList.requestedAt": "",
"components.RequestModal.request": "リクエストする", "components.RequestList.requests": "",
"components.RequestModal.requestCancel": "", "components.RequestList.showingresults": "",
"components.RequestModal.requestSuccess": "", "components.RequestList.status": "",
"components.RequestModal.requestadmin": "このリクエストが今すぐ承認致します。よろしいですか?", "components.RequestModal.cancel": "チャンセルリクエスト",
"components.RequestModal.requestfrom": "", "components.RequestModal.cancelling": "",
"components.RequestModal.requesting": "", "components.RequestModal.cancelrequest": "このリクエストをキャンセルしてよろしいですか?",
"components.RequestModal.requestseasons": "", "components.RequestModal.close": "",
"components.RequestModal.requesttitle": "", "components.RequestModal.extras": "",
"components.RequestModal.season": "", "components.RequestModal.notrequested": "",
"components.RequestModal.seasonnumber": "", "components.RequestModal.numberofepisodes": "",
"components.RequestModal.selectseason": "", "components.RequestModal.pendingrequest": "",
"components.RequestModal.status": "状態", "components.RequestModal.request": "リクエストする",
"components.Search.searchresults": "", "components.RequestModal.requestCancel": "",
"components.Settings.Notifications.agentenabled": "", "components.RequestModal.requestSuccess": "",
"components.Settings.Notifications.authPass": "", "components.RequestModal.requestadmin": "このリクエストが今すぐ承認致します。よろしいですか?",
"components.Settings.Notifications.authUser": "", "components.RequestModal.requestfrom": "",
"components.Settings.Notifications.emailsender": "", "components.RequestModal.requesting": "",
"components.Settings.Notifications.enableSsl": "", "components.RequestModal.requestseasons": "",
"components.Settings.Notifications.save": "", "components.RequestModal.requesttitle": "",
"components.Settings.Notifications.saving": "", "components.RequestModal.season": "",
"components.Settings.Notifications.smtpHost": "", "components.RequestModal.seasonnumber": "",
"components.Settings.Notifications.smtpPort": "", "components.RequestModal.selectseason": "",
"components.Settings.Notifications.validationFromRequired": "", "components.RequestModal.status": "状態",
"components.Settings.Notifications.validationSmtpHostRequired": "", "components.Search.searchresults": "",
"components.Settings.Notifications.validationSmtpPortRequired": "", "components.Settings.Notifications.agentenabled": "",
"components.Settings.Notifications.validationWebhookUrlRequired": "", "components.Settings.Notifications.authPass": "",
"components.Settings.Notifications.webhookUrl": "", "components.Settings.Notifications.authUser": "",
"components.Settings.Notifications.webhookUrlPlaceholder": "", "components.Settings.Notifications.emailsender": "",
"components.Settings.RadarrModal.add": "", "components.Settings.Notifications.enableSsl": "",
"components.Settings.RadarrModal.apiKey": "", "components.Settings.Notifications.save": "",
"components.Settings.RadarrModal.apiKeyPlaceholder": "", "components.Settings.Notifications.saving": "",
"components.Settings.RadarrModal.baseUrl": "", "components.Settings.Notifications.smtpHost": "",
"components.Settings.RadarrModal.baseUrlPlaceholder": "", "components.Settings.Notifications.smtpPort": "",
"components.Settings.RadarrModal.createradarr": "", "components.Settings.Notifications.validationFromRequired": "",
"components.Settings.RadarrModal.defaultserver": "", "components.Settings.Notifications.validationSmtpHostRequired": "",
"components.Settings.RadarrModal.editradarr": "", "components.Settings.Notifications.validationSmtpPortRequired": "",
"components.Settings.RadarrModal.hostname": "", "components.Settings.Notifications.validationWebhookUrlRequired": "",
"components.Settings.RadarrModal.minimumAvailability": "", "components.Settings.Notifications.webhookUrl": "",
"components.Settings.RadarrModal.port": "", "components.Settings.Notifications.webhookUrlPlaceholder": "",
"components.Settings.RadarrModal.qualityprofile": "", "components.Settings.RadarrModal.add": "",
"components.Settings.RadarrModal.rootfolder": "", "components.Settings.RadarrModal.apiKey": "",
"components.Settings.RadarrModal.save": "", "components.Settings.RadarrModal.apiKeyPlaceholder": "",
"components.Settings.RadarrModal.saving": "", "components.Settings.RadarrModal.baseUrl": "",
"components.Settings.RadarrModal.selectMinimumAvailability": "", "components.Settings.RadarrModal.baseUrlPlaceholder": "",
"components.Settings.RadarrModal.selectQualityProfile": "", "components.Settings.RadarrModal.createradarr": "",
"components.Settings.RadarrModal.selectRootFolder": "", "components.Settings.RadarrModal.defaultserver": "",
"components.Settings.RadarrModal.server4k": "", "components.Settings.RadarrModal.editradarr": "",
"components.Settings.RadarrModal.servername": "", "components.Settings.RadarrModal.hostname": "",
"components.Settings.RadarrModal.servernamePlaceholder": "", "components.Settings.RadarrModal.minimumAvailability": "",
"components.Settings.RadarrModal.ssl": "", "components.Settings.RadarrModal.port": "",
"components.Settings.RadarrModal.test": "", "components.Settings.RadarrModal.qualityprofile": "",
"components.Settings.RadarrModal.testing": "", "components.Settings.RadarrModal.rootfolder": "",
"components.Settings.RadarrModal.toastRadarrTestFailure": "", "components.Settings.RadarrModal.save": "",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "", "components.Settings.RadarrModal.saving": "",
"components.Settings.RadarrModal.validationApiKeyRequired": "", "components.Settings.RadarrModal.selectMinimumAvailability": "",
"components.Settings.RadarrModal.validationHostnameRequired": "", "components.Settings.RadarrModal.selectQualityProfile": "",
"components.Settings.RadarrModal.validationPortRequired": "", "components.Settings.RadarrModal.selectRootFolder": "",
"components.Settings.RadarrModal.validationProfileRequired": "", "components.Settings.RadarrModal.server4k": "",
"components.Settings.RadarrModal.validationRootFolderRequired": "", "components.Settings.RadarrModal.servername": "",
"components.Settings.SonarrModal.add": "", "components.Settings.RadarrModal.servernamePlaceholder": "",
"components.Settings.SonarrModal.apiKey": "", "components.Settings.RadarrModal.ssl": "",
"components.Settings.SonarrModal.apiKeyPlaceholder": "", "components.Settings.RadarrModal.test": "",
"components.Settings.SonarrModal.baseUrl": "", "components.Settings.RadarrModal.testing": "",
"components.Settings.SonarrModal.baseUrlPlaceholder": "", "components.Settings.RadarrModal.toastRadarrTestFailure": "",
"components.Settings.SonarrModal.createsonarr": "", "components.Settings.RadarrModal.toastRadarrTestSuccess": "",
"components.Settings.SonarrModal.defaultserver": "", "components.Settings.RadarrModal.validationApiKeyRequired": "",
"components.Settings.SonarrModal.editsonarr": "", "components.Settings.RadarrModal.validationHostnameRequired": "",
"components.Settings.SonarrModal.hostname": "", "components.Settings.RadarrModal.validationPortRequired": "",
"components.Settings.SonarrModal.port": "", "components.Settings.RadarrModal.validationProfileRequired": "",
"components.Settings.SonarrModal.qualityprofile": "", "components.Settings.RadarrModal.validationRootFolderRequired": "",
"components.Settings.SonarrModal.rootfolder": "", "components.Settings.SonarrModal.add": "",
"components.Settings.SonarrModal.save": "", "components.Settings.SonarrModal.apiKey": "",
"components.Settings.SonarrModal.saving": "", "components.Settings.SonarrModal.apiKeyPlaceholder": "",
"components.Settings.SonarrModal.seasonfolders": "", "components.Settings.SonarrModal.baseUrl": "",
"components.Settings.SonarrModal.selectQualityProfile": "", "components.Settings.SonarrModal.baseUrlPlaceholder": "",
"components.Settings.SonarrModal.selectRootFolder": "", "components.Settings.SonarrModal.createsonarr": "",
"components.Settings.SonarrModal.server4k": "", "components.Settings.SonarrModal.defaultserver": "",
"components.Settings.SonarrModal.servername": "", "components.Settings.SonarrModal.editsonarr": "",
"components.Settings.SonarrModal.servernamePlaceholder": "", "components.Settings.SonarrModal.hostname": "",
"components.Settings.SonarrModal.ssl": "", "components.Settings.SonarrModal.port": "",
"components.Settings.SonarrModal.test": "", "components.Settings.SonarrModal.qualityprofile": "",
"components.Settings.SonarrModal.testing": "", "components.Settings.SonarrModal.rootfolder": "",
"components.Settings.SonarrModal.toastRadarrTestFailure": "", "components.Settings.SonarrModal.save": "",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "", "components.Settings.SonarrModal.saving": "",
"components.Settings.SonarrModal.validationApiKeyRequired": "", "components.Settings.SonarrModal.seasonfolders": "",
"components.Settings.SonarrModal.validationHostnameRequired": "", "components.Settings.SonarrModal.selectQualityProfile": "",
"components.Settings.SonarrModal.validationPortRequired": "", "components.Settings.SonarrModal.selectRootFolder": "",
"components.Settings.SonarrModal.validationProfileRequired": "", "components.Settings.SonarrModal.server4k": "",
"components.Settings.SonarrModal.validationRootFolderRequired": "", "components.Settings.SonarrModal.servername": "",
"components.Settings.activeProfile": "", "components.Settings.SonarrModal.servernamePlaceholder": "",
"components.Settings.addradarr": "", "components.Settings.SonarrModal.ssl": "",
"components.Settings.address": "", "components.Settings.SonarrModal.test": "",
"components.Settings.addsonarr": "", "components.Settings.SonarrModal.testing": "",
"components.Settings.apikey": "", "components.Settings.SonarrModal.toastRadarrTestFailure": "",
"components.Settings.applicationurl": "", "components.Settings.SonarrModal.toastRadarrTestSuccess": "",
"components.Settings.cancelscan": "", "components.Settings.SonarrModal.validationApiKeyRequired": "",
"components.Settings.copied": "", "components.Settings.SonarrModal.validationHostnameRequired": "",
"components.Settings.currentlibrary": "", "components.Settings.SonarrModal.validationPortRequired": "",
"components.Settings.default": "", "components.Settings.SonarrModal.validationProfileRequired": "",
"components.Settings.default4k": "", "components.Settings.SonarrModal.validationRootFolderRequired": "",
"components.Settings.delete": "", "components.Settings.activeProfile": "",
"components.Settings.deleteserverconfirm": "", "components.Settings.addradarr": "",
"components.Settings.edit": "", "components.Settings.address": "",
"components.Settings.generalsettings": "", "components.Settings.addsonarr": "",
"components.Settings.generalsettingsDescription": "", "components.Settings.apikey": "",
"components.Settings.hostname": "", "components.Settings.applicationurl": "",
"components.Settings.jobname": "", "components.Settings.cancelscan": "",
"components.Settings.librariesRemaining": "", "components.Settings.copied": "",
"components.Settings.manualscan": "", "components.Settings.currentlibrary": "",
"components.Settings.manualscanDescription": "", "components.Settings.default": "",
"components.Settings.menuAbout": "", "components.Settings.default4k": "",
"components.Settings.menuGeneralSettings": "", "components.Settings.delete": "",
"components.Settings.menuJobs": "", "components.Settings.deleteserverconfirm": "",
"components.Settings.menuLogs": "", "components.Settings.edit": "",
"components.Settings.menuNotifications": "", "components.Settings.generalsettings": "",
"components.Settings.menuPlexSettings": "", "components.Settings.generalsettingsDescription": "",
"components.Settings.menuServices": "", "components.Settings.hostname": "",
"components.Settings.nextexecution": "", "components.Settings.jobname": "",
"components.Settings.notificationsettings": "", "components.Settings.librariesRemaining": "",
"components.Settings.notificationsettingsDescription": "", "components.Settings.manualscan": "",
"components.Settings.notrunning": "", "components.Settings.manualscanDescription": "",
"components.Settings.plexlibraries": "", "components.Settings.menuAbout": "",
"components.Settings.plexlibrariesDescription": "", "components.Settings.menuGeneralSettings": "",
"components.Settings.plexsettings": "", "components.Settings.menuJobs": "",
"components.Settings.plexsettingsDescription": "", "components.Settings.menuLogs": "",
"components.Settings.port": "", "components.Settings.menuNotifications": "",
"components.Settings.radarrSettingsDescription": "", "components.Settings.menuPlexSettings": "",
"components.Settings.radarrsettings": "", "components.Settings.menuServices": "",
"components.Settings.runnow": "", "components.Settings.nextexecution": "",
"components.Settings.save": "", "components.Settings.notificationsettings": "",
"components.Settings.saving": "", "components.Settings.notificationsettingsDescription": "",
"components.Settings.servername": "", "components.Settings.notrunning": "",
"components.Settings.servernamePlaceholder": "", "components.Settings.plexlibraries": "",
"components.Settings.sonarrSettingsDescription": "", "components.Settings.plexlibrariesDescription": "",
"components.Settings.sonarrsettings": "", "components.Settings.plexsettings": "",
"components.Settings.ssl": "", "components.Settings.plexsettingsDescription": "",
"components.Settings.startscan": "", "components.Settings.port": "",
"components.Settings.sync": "", "components.Settings.radarrSettingsDescription": "",
"components.Settings.syncing": "", "components.Settings.radarrsettings": "",
"components.Setup.configureplex": "", "components.Settings.runnow": "",
"components.Setup.configureservices": "", "components.Settings.save": "",
"components.Setup.continue": "", "components.Settings.saving": "",
"components.Setup.finish": "", "components.Settings.servername": "",
"components.Setup.finishing": "", "components.Settings.servernamePlaceholder": "",
"components.Setup.loginwithplex": "", "components.Settings.sonarrSettingsDescription": "",
"components.Setup.signinMessage": "", "components.Settings.sonarrsettings": "",
"components.Setup.welcome": "", "components.Settings.ssl": "",
"components.Slider.noresults": "", "components.Settings.startscan": "",
"components.TitleCard.movie": "", "components.Settings.sync": "",
"components.TitleCard.tvshow": "", "components.Settings.syncing": "",
"components.TvDetails.approve": "", "components.Setup.configureplex": "",
"components.TvDetails.approverequests": "", "components.Setup.configureservices": "",
"components.TvDetails.available": "", "components.Setup.continue": "",
"components.TvDetails.cancelrequest": "チャンセルリクエスト", "components.Setup.finish": "",
"components.TvDetails.cast": "キャスト", "components.Setup.finishing": "",
"components.TvDetails.decline": "", "components.Setup.loginwithplex": "",
"components.TvDetails.declinerequests": "", "components.Setup.signinMessage": "",
"components.TvDetails.manageModalClearMedia": "", "components.Setup.welcome": "",
"components.TvDetails.manageModalClearMediaWarning": "", "components.Slider.noresults": "",
"components.TvDetails.manageModalNoRequests": "", "components.TitleCard.movie": "",
"components.TvDetails.manageModalRequests": "リクエスト", "components.TitleCard.tvshow": "",
"components.TvDetails.manageModalTitle": "", "components.TvDetails.approve": "",
"components.TvDetails.originallanguage": "言語", "components.TvDetails.approverequests": "",
"components.TvDetails.overview": "ストーリー", "components.TvDetails.available": "",
"components.TvDetails.overviewunavailable": "", "components.TvDetails.cancelrequest": "チャンセルリクエスト",
"components.TvDetails.pending": "リクエスト中", "components.TvDetails.cast": "キャスト",
"components.TvDetails.recommendations": "オススメの作品", "components.TvDetails.decline": "",
"components.TvDetails.recommendationssubtext": "", "components.TvDetails.declinerequests": "",
"components.TvDetails.request": "リクエストする", "components.TvDetails.manageModalClearMedia": "",
"components.TvDetails.requestmore": "", "components.TvDetails.manageModalClearMediaWarning": "",
"components.TvDetails.similar": "", "components.TvDetails.manageModalNoRequests": "",
"components.TvDetails.similarsubtext": "", "components.TvDetails.manageModalRequests": "リクエスト",
"components.TvDetails.status": "状態", "components.TvDetails.manageModalTitle": "",
"components.TvDetails.unavailable": "", "components.TvDetails.originallanguage": "言語",
"components.TvDetails.userrating": "ユーザー評価", "components.TvDetails.overview": "ストーリー",
"components.UserEdit.admin": "", "components.TvDetails.overviewunavailable": "",
"components.UserEdit.adminDescription": "", "components.TvDetails.pending": "リクエスト中",
"components.UserEdit.autoapprove": "", "components.TvDetails.recommendations": "オススメの作品",
"components.UserEdit.autoapproveDescription": "", "components.TvDetails.recommendationssubtext": "",
"components.UserEdit.avatar": "", "components.TvDetails.request": "リクエストする",
"components.UserEdit.edituser": "", "components.TvDetails.requestmore": "",
"components.UserEdit.email": "", "components.TvDetails.similar": "",
"components.UserEdit.managerequests": "", "components.TvDetails.similarsubtext": "",
"components.UserEdit.managerequestsDescription": "", "components.TvDetails.status": "状態",
"components.UserEdit.permissions": "", "components.TvDetails.unavailable": "",
"components.UserEdit.request": "リクエストする", "components.TvDetails.userrating": "ユーザー評価",
"components.UserEdit.requestDescription": "", "components.UserEdit.admin": "",
"components.UserEdit.save": "", "components.UserEdit.adminDescription": "",
"components.UserEdit.saving": "", "components.UserEdit.autoapprove": "",
"components.UserEdit.settings": "", "components.UserEdit.autoapproveDescription": "",
"components.UserEdit.settingsDescription": "", "components.UserEdit.avatar": "",
"components.UserEdit.userfail": "", "components.UserEdit.edituser": "",
"components.UserEdit.username": "", "components.UserEdit.email": "",
"components.UserEdit.users": "", "components.UserEdit.managerequests": "",
"components.UserEdit.usersDescription": "", "components.UserEdit.managerequestsDescription": "",
"components.UserEdit.usersaved": "", "components.UserEdit.permissions": "",
"components.UserEdit.vote": "", "components.UserEdit.request": "リクエストする",
"components.UserEdit.voteDescription": "", "components.UserEdit.requestDescription": "",
"components.UserList.admin": "", "components.UserEdit.save": "",
"components.UserList.created": "", "components.UserEdit.saving": "",
"components.UserList.delete": "", "components.UserEdit.settings": "",
"components.UserList.edit": "", "components.UserEdit.settingsDescription": "",
"components.UserList.lastupdated": "", "components.UserEdit.userfail": "",
"components.UserList.plexuser": "", "components.UserEdit.username": "",
"components.UserList.role": "", "components.UserEdit.users": "",
"components.UserList.totalrequests": "", "components.UserEdit.usersDescription": "",
"components.UserList.user": "", "components.UserEdit.usersaved": "",
"components.UserList.userlist": "", "components.UserEdit.vote": "",
"components.UserList.username": "", "components.UserEdit.voteDescription": "",
"components.UserList.usertype": "", "components.UserList.admin": "",
"i18n.approve": "", "components.UserList.created": "",
"i18n.approved": "", "components.UserList.delete": "",
"i18n.available": "", "components.UserList.edit": "",
"i18n.cancel": "", "components.UserList.lastupdated": "",
"i18n.decline": "", "components.UserList.plexuser": "",
"i18n.declined": "", "components.UserList.role": "",
"i18n.movies": "", "components.UserList.totalrequests": "",
"i18n.partiallyavailable": "", "components.UserList.user": "",
"i18n.pending": "リクエスト中", "components.UserList.userlist": "",
"i18n.processing": "", "components.UserList.username": "",
"i18n.tvshows": "", "components.UserList.usertype": "",
"i18n.unavailable": "", "i18n.approve": "",
"pages.internalServerError": "", "i18n.approved": "",
"pages.oops": "ああ", "i18n.available": "",
"pages.pageNotFound": "", "i18n.cancel": "",
"pages.returnHome": "ホームへ戻る", "i18n.decline": "",
"pages.serviceUnavailable": "", "i18n.declined": "",
"pages.somethingWentWrong": "" "i18n.delete": "",
"i18n.movies": "",
"i18n.partiallyavailable": "",
"i18n.pending": "リクエスト中",
"i18n.processing": "",
"i18n.tvshows": "",
"i18n.unavailable": "",
"pages.internalServerError": "",
"pages.oops": "ああ",
"pages.pageNotFound": "",
"pages.returnHome": "ホームへ戻る",
"pages.serviceUnavailable": "",
"pages.somethingWentWrong": ""
} }

@ -100,13 +100,11 @@ CoreApp.getInitialProps = async (initialProps) => {
if (ctx.res) { if (ctx.res) {
// Check if app is initialized and redirect if necessary // Check if app is initialized and redirect if necessary
let initialized = true;
const response = await axios.get<{ initialized: boolean }>( const response = await axios.get<{ initialized: boolean }>(
`http://localhost:${process.env.PORT || 3000}/api/v1/settings/public` `http://localhost:${process.env.PORT || 3000}/api/v1/settings/public`
); );
initialized = response.data.initialized; const initialized = response.data.initialized;
if (!initialized) { if (!initialized) {
if (!router.pathname.match(/(setup|login\/plex)/)) { if (!router.pathname.match(/(setup|login\/plex)/)) {
@ -145,7 +143,7 @@ CoreApp.getInitialProps = async (initialProps) => {
const cookies = parseCookies(ctx); const cookies = parseCookies(ctx);
if (!!cookies.locale) { if (cookies.locale) {
locale = cookies.locale; locale = cookies.locale;
} }
} }

@ -65,7 +65,7 @@ Error.getInitialProps = async ({ res, err }): Promise<ErrorProps> => {
// Apologies for how gross ternary is but this is just temporary. Honestly, // Apologies for how gross ternary is but this is just temporary. Honestly,
// blame the nextjs docs // blame the nextjs docs
let statusCode: Undefinable<number>; let statusCode: Undefinable<number>;
if (!!res) { if (res) {
statusCode = res.statusCode; statusCode = res.statusCode;
} else { } else {
statusCode = err ? err.statusCode : undefined; statusCode = err ? err.statusCode : undefined;

@ -0,0 +1,9 @@
import React from 'react';
import type { NextPage } from 'next';
import RequestList from '../../components/RequestList';
const RequestsPage: NextPage = () => {
return <RequestList />;
};
export default RequestsPage;

@ -6,7 +6,7 @@ import useRouteGuard from '../../hooks/useRouteGuard';
import { Permission } from '../../hooks/useUser'; import { Permission } from '../../hooks/useUser';
const SettingsPage: NextPage = () => { const SettingsPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS); useRouteGuard(Permission.MANAGE_SETTINGS);
return ( return (
<SettingsLayout> <SettingsLayout>
<SettingsMain /> <SettingsMain />

@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard'; import useRouteGuard from '../../hooks/useRouteGuard';
const SettingsMainPage: NextPage = () => { const SettingsMainPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS); useRouteGuard(Permission.MANAGE_SETTINGS);
return ( return (
<SettingsLayout> <SettingsLayout>
<SettingsJobs /> <SettingsJobs />

@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard'; import useRouteGuard from '../../hooks/useRouteGuard';
const SettingsMainPage: NextPage = () => { const SettingsMainPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS); useRouteGuard(Permission.MANAGE_SETTINGS);
return ( return (
<SettingsLayout> <SettingsLayout>
<SettingsMain /> <SettingsMain />

@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard'; import useRouteGuard from '../../hooks/useRouteGuard';
const PlexSettingsPage: NextPage = () => { const PlexSettingsPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS); useRouteGuard(Permission.MANAGE_SETTINGS);
return ( return (
<SettingsLayout> <SettingsLayout>
<SettingsPlex /> <SettingsPlex />

@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard'; import useRouteGuard from '../../hooks/useRouteGuard';
const ServicesSettingsPage: NextPage = () => { const ServicesSettingsPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS); useRouteGuard(Permission.MANAGE_SETTINGS);
return ( return (
<SettingsLayout> <SettingsLayout>
<SettingsServices /> <SettingsServices />

@ -57,19 +57,15 @@ class PlexOAuth {
'You must initialize the plex headers clientside to login' 'You must initialize the plex headers clientside to login'
); );
} }
try { const response = await axios.post(
const response = await axios.post( 'https://plex.tv/api/v2/pins?strong=true',
'https://plex.tv/api/v2/pins?strong=true', undefined,
undefined, { headers: this.plexHeaders }
{ headers: this.plexHeaders } );
);
this.pin = { id: response.data.id, code: response.data.code }; this.pin = { id: response.data.id, code: response.data.code };
return this.pin; return this.pin;
} catch (e) {
throw e;
}
} }
public preparePopup(): void { public preparePopup(): void {
@ -77,42 +73,38 @@ class PlexOAuth {
} }
public async login(): Promise<string> { public async login(): Promise<string> {
try { this.initializeHeaders();
this.initializeHeaders(); await this.getPin();
await this.getPin();
if (!this.plexHeaders || !this.pin) { if (!this.plexHeaders || !this.pin) {
throw new Error('Unable to call login if class is not initialized.'); throw new Error('Unable to call login if class is not initialized.');
} }
const params = { const params = {
clientID: this.plexHeaders['X-Plex-Client-Identifier'], clientID: this.plexHeaders['X-Plex-Client-Identifier'],
'context[device][product]': this.plexHeaders['X-Plex-Product'], 'context[device][product]': this.plexHeaders['X-Plex-Product'],
'context[device][version]': this.plexHeaders['X-Plex-Version'], 'context[device][version]': this.plexHeaders['X-Plex-Version'],
'context[device][platform]': this.plexHeaders['X-Plex-Platform'], 'context[device][platform]': this.plexHeaders['X-Plex-Platform'],
'context[device][platformVersion]': this.plexHeaders[ 'context[device][platformVersion]': this.plexHeaders[
'X-Plex-Platform-Version' 'X-Plex-Platform-Version'
], ],
'context[device][device]': this.plexHeaders['X-Plex-Device'], 'context[device][device]': this.plexHeaders['X-Plex-Device'],
'context[device][deviceName]': this.plexHeaders['X-Plex-Device-Name'], 'context[device][deviceName]': this.plexHeaders['X-Plex-Device-Name'],
'context[device][model]': this.plexHeaders['X-Plex-Model'], 'context[device][model]': this.plexHeaders['X-Plex-Model'],
'context[device][screenResolution]': this.plexHeaders[ 'context[device][screenResolution]': this.plexHeaders[
'X-Plex-Device-Screen-Resolution' 'X-Plex-Device-Screen-Resolution'
], ],
'context[device][layout]': 'desktop', 'context[device][layout]': 'desktop',
code: this.pin.code, code: this.pin.code,
}; };
if (this.popup) {
this.popup.location.href = `https://app.plex.tv/auth/#!?${this.encodeData(
params
)}`;
}
return this.pinPoll(); if (this.popup) {
} catch (e) { this.popup.location.href = `https://app.plex.tv/auth/#!?${this.encodeData(
throw e; params
)}`;
} }
return this.pinPoll();
} }
private async pinPoll(): Promise<string> { private async pinPoll(): Promise<string> {
@ -131,9 +123,9 @@ class PlexOAuth {
); );
if (response.data?.authToken) { if (response.data?.authToken) {
this.authToken = response.data.authToken; this.authToken = response.data.authToken as string;
this.closePopup(); this.closePopup();
resolve(response.data.authToken); resolve(this.authToken);
} else if (!response.data?.authToken && !this.popup?.closed) { } else if (!response.data?.authToken && !this.popup?.closed) {
setTimeout(executePoll, 1000, resolve, reject); setTimeout(executePoll, 1000, resolve, reject);
} else { } else {

@ -1,4 +1,5 @@
module.exports = { module.exports = {
ignoreFiles: ['**/*.js'],
rules: { rules: {
'at-rule-no-unknown': [ 'at-rule-no-unknown': [
true, true,

@ -62,10 +62,10 @@
call-me-maybe "^1.0.1" call-me-maybe "^1.0.1"
js-yaml "^3.13.1" js-yaml "^3.13.1"
"@babel/cli@^7.11.6": "@babel/cli@^7.12.8":
version "7.11.6" version "7.12.8"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.11.6.tgz#1fcbe61c2a6900c3539c06ee58901141f3558482" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.12.8.tgz#3b24ed2fd5da353ee6f19e8935ff8c93b5fe8430"
integrity sha512-+w7BZCvkewSmaRM6H4L2QM3RL90teqEIHDIFXAmrW33+0jhlymnDAEdqVeCZATvxhQuio1ifoGVlJJbIiH9Ffg== integrity sha512-/6nQj11oaGhLmZiuRUfxsujiPDc9BBReemiXgIbxc+M5W+MIiFKYwvNDJvBfnGKNsJTKbUfEheKc9cwoPHAVQA==
dependencies: dependencies:
commander "^4.0.1" commander "^4.0.1"
convert-source-map "^1.1.0" convert-source-map "^1.1.0"
@ -76,7 +76,8 @@
slash "^2.0.0" slash "^2.0.0"
source-map "^0.5.0" source-map "^0.5.0"
optionalDependencies: optionalDependencies:
chokidar "^2.1.8" "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents"
chokidar "^3.4.0"
"@babel/code-frame@7.10.4", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": "@babel/code-frame@7.10.4", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
version "7.10.4" version "7.10.4"
@ -1310,10 +1311,10 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@eslint/eslintrc@^0.1.3": "@eslint/eslintrc@^0.2.2":
version "0.1.3" version "0.2.2"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76"
integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==
dependencies: dependencies:
ajv "^6.12.4" ajv "^6.12.4"
debug "^4.1.1" debug "^4.1.1"
@ -1326,6 +1327,13 @@
minimatch "^3.0.4" minimatch "^3.0.4"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@formatjs/ecma402-abstract@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.5.0.tgz#759c8f11ff45e96f8fb58741e7fbdb41096d5ddd"
integrity sha512-wXv36yo+mfWllweN0Fq7sUs7PUiNopn7I0JpLTe3hGu6ZMR4CV7LqK1llhB18pndwpKoafQKb1et2DCJAOW20Q==
dependencies:
tslib "^2.0.1"
"@formatjs/ecma402-abstract@^1.2.1", "@formatjs/ecma402-abstract@^1.2.4": "@formatjs/ecma402-abstract@^1.2.1", "@formatjs/ecma402-abstract@^1.2.4":
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.4.tgz#0f11e0309bc885d53ddc823e36d04d520fda7674" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.4.tgz#0f11e0309bc885d53ddc823e36d04d520fda7674"
@ -1378,6 +1386,15 @@
intl-messageformat-parser "^6.0.9" intl-messageformat-parser "^6.0.9"
tslib "^2.0.1" tslib "^2.0.1"
"@formatjs/ts-transformer@2.12.10":
version "2.12.10"
resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.12.10.tgz#4f8758ea89e2536239b573da98f99454a4952ebf"
integrity sha512-H8mtPQcyXxLo3GJGkNVj3ZlmebeqxQfVTIvGsdpE1oXKZ/SxKqvC7ZeHlbZUyXUEiRwdJ4Hfsgw1QzsmTJnicw==
dependencies:
intl-messageformat-parser "6.0.18"
tslib "^2.0.1"
typescript "^4.0"
"@formatjs/ts-transformer@^2.10.0", "@formatjs/ts-transformer@^2.6.0": "@formatjs/ts-transformer@^2.10.0", "@formatjs/ts-transformer@^2.6.0":
version "2.10.0" version "2.10.0"
resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.10.0.tgz#06f292b6cbcea661e2cecf7b8945ac59f21b7c93" resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.10.0.tgz#06f292b6cbcea661e2cecf7b8945ac59f21b7c93"
@ -1477,6 +1494,23 @@
resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-10.0.3.tgz#276bec60eae18768f96baf8a52f668f657f50ab4" resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-10.0.3.tgz#276bec60eae18768f96baf8a52f668f657f50ab4"
integrity sha512-XtzzPX2R4+MIyu1waEQUo2tiNwWVEkmObA6pboRCDTPOs4Ri8ckaIE08lN5A5opyF6GVN+IEq/J8KQrgsePsZQ== integrity sha512-XtzzPX2R4+MIyu1waEQUo2tiNwWVEkmObA6pboRCDTPOs4Ri8ckaIE08lN5A5opyF6GVN+IEq/J8KQrgsePsZQ==
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents":
version "2.1.8-no-fsevents"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz#da7c3996b8e6e19ebd14d82eaced2313e7769f9b"
integrity sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==
dependencies:
anymatch "^2.0.0"
async-each "^1.0.1"
braces "^2.3.2"
glob-parent "^3.1.0"
inherits "^2.0.3"
is-binary-path "^1.0.0"
is-glob "^4.0.0"
normalize-path "^3.0.0"
path-is-absolute "^1.0.0"
readdirp "^2.2.1"
upath "^1.1.1"
"@nodelib/fs.scandir@2.1.3": "@nodelib/fs.scandir@2.1.3":
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
@ -1927,11 +1961,6 @@
resolved "https://registry.yarnpkg.com/@types/emoji-regex/-/emoji-regex-8.0.0.tgz#df215c9ff818e071087fb8e7e6e74c4cb42a1303" resolved "https://registry.yarnpkg.com/@types/emoji-regex/-/emoji-regex-8.0.0.tgz#df215c9ff818e071087fb8e7e6e74c4cb42a1303"
integrity sha512-iacbaYN9IWWrGWTwlYLVOeUtN/e4cjN9Uh6v7Yo1Qa/vJzeSQeh10L/erBBSl53BTmbnQ07vsWp8mmNHGI4WbQ== integrity sha512-iacbaYN9IWWrGWTwlYLVOeUtN/e4cjN9Uh6v7Yo1Qa/vJzeSQeh10L/erBBSl53BTmbnQ07vsWp8mmNHGI4WbQ==
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
"@types/eslint@^7.2.0": "@types/eslint@^7.2.0":
version "7.2.2" version "7.2.2"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.2.tgz#c88426b896efeb0b2732a92431ce8aa7ec0dee61" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.2.tgz#c88426b896efeb0b2732a92431ce8aa7ec0dee61"
@ -2002,10 +2031,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
"@types/lodash@^4.14.161": "@types/lodash@^4.14.165":
version "4.14.161" version "4.14.165"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f"
integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==
"@types/mime@*": "@types/mime@*":
version "2.0.3" version "2.0.3"
@ -2029,12 +2058,12 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/node@*", "@types/node@^14.10.0": "@types/node@*":
version "14.10.0" version "14.10.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.0.tgz#15815dff82c8dc30827f6b1286f865902945095a" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.0.tgz#15815dff82c8dc30827f6b1286f865902945095a"
integrity sha512-SOIyrdADB4cq6eY1F+9iU48iIomFAPltu11LCvA9PKcyEwHadjCFzNVPotAR+oEJA0bCP4Xvvgy+vwu1ZjVh8g== integrity sha512-SOIyrdADB4cq6eY1F+9iU48iIomFAPltu11LCvA9PKcyEwHadjCFzNVPotAR+oEJA0bCP4Xvvgy+vwu1ZjVh8g==
"@types/node@>= 8": "@types/node@>= 8", "@types/node@^14.14.10":
version "14.14.10" version "14.14.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785"
integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ== integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==
@ -2076,10 +2105,10 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/react-dom@^16.9.8": "@types/react-dom@^17.0.0":
version "16.9.8" version "17.0.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a"
integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== integrity sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
@ -2097,7 +2126,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^16.9.49": "@types/react@*":
version "16.9.49" version "16.9.49"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872"
integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g== integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==
@ -2105,6 +2134,14 @@
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/react@^17.0.0":
version "17.0.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/retry@^0.12.0": "@types/retry@^0.12.0":
version "0.12.0" version "0.12.0"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
@ -2138,10 +2175,10 @@
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
"@types/xml2js@^0.4.5": "@types/xml2js@^0.4.7":
version "0.4.5" version "0.4.7"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.5.tgz#d21759b056f282d9c7066f15bbf5c19b908f22fa" resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.7.tgz#cd5b6c67bbec741ac625718a76e6cb99bc34365e"
integrity sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w== integrity sha512-f5VOKSMEE0O+/L54FHwA/a7vcx9mHeSDM71844yHCOhh8Cin2xQa0UFw0b7Vc5hoZ3Ih6ZHaDobjfLih4tWPNw==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
@ -2150,99 +2187,87 @@
resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245" resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245"
integrity sha512-QcJ5ZczaXAqbVD3o8mw/mEBhRvO5UAdTtbvgwL/OgoWubvNBh6/MxLBAigtcgIFaq3shon9m3POIxQaLQt4fxQ== integrity sha512-QcJ5ZczaXAqbVD3o8mw/mEBhRvO5UAdTtbvgwL/OgoWubvNBh6/MxLBAigtcgIFaq3shon9m3POIxQaLQt4fxQ==
"@types/yup@^0.29.9": "@types/yup@^0.29.10":
version "0.29.9" version "0.29.10"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.9.tgz#e2015187ae5739fd3b791b3b7ab9094f2aa5a474" resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.10.tgz#1bfa4c4a47a6f57fcc8510948757b9e47c0d6ca3"
integrity sha512-ZtjjlrHuHTYctHDz3c8XgInjj0v+Hahe32N/4cDa2banibf9w6aAgxwx0jZtBjKKzmGIU4NXhofEsBW1BbqrNg== integrity sha512-kRKRZaWkxxnOK7H5C4oWqhCw9ID1QF3cBZ2oAPoXYsjIncwgpDGigWtXGjZ91t+hsc3cvPdBci9YoJo1A96CYg==
"@typescript-eslint/eslint-plugin@^4.0.0": "@typescript-eslint/eslint-plugin@^4.9.1":
version "4.0.0" version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.0.0.tgz#99349a501447fed91de18346705c0c65cf603bee" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz#66758cbe129b965fe9c63b04b405d0cf5280868b"
integrity sha512-5e6q1TR7gS2P+8W2xndCu7gBh3BzmYEo70OyIdsmCmknHha/yNbz2vdevl+tP1uoaMOcrzg4gyrAijuV3DDBHA== integrity sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ==
dependencies: dependencies:
"@typescript-eslint/experimental-utils" "4.0.0" "@typescript-eslint/experimental-utils" "4.9.1"
"@typescript-eslint/scope-manager" "4.0.0" "@typescript-eslint/scope-manager" "4.9.1"
debug "^4.1.1" debug "^4.1.1"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
regexpp "^3.0.0" regexpp "^3.0.0"
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@3.10.1": "@typescript-eslint/experimental-utils@4.9.1":
version "3.10.1" version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz#86633e8395191d65786a808dc3df030a55267ae2"
integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== integrity sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg==
dependencies: dependencies:
"@types/json-schema" "^7.0.3" "@types/json-schema" "^7.0.3"
"@typescript-eslint/types" "3.10.1" "@typescript-eslint/scope-manager" "4.9.1"
"@typescript-eslint/typescript-estree" "3.10.1" "@typescript-eslint/types" "4.9.1"
"@typescript-eslint/typescript-estree" "4.9.1"
eslint-scope "^5.0.0" eslint-scope "^5.0.0"
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
"@typescript-eslint/experimental-utils@4.0.0": "@typescript-eslint/parser@^4.9.1":
version "4.0.0" version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.0.0.tgz#fbec21a3b5ab59127edb6ce2e139ed378cc50eb5" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.9.1.tgz#2d74c4db5dd5117379a9659081a4d1ec02629055"
integrity sha512-hbX6zR+a/vcpFVNJYN/Nbd7gmaMosDTxHEKcvmhWeWcq/0UDifrqmCfkkodbAKL46Fn4ekSBMTyq2zlNDzcQxw== integrity sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g==
dependencies: dependencies:
"@types/json-schema" "^7.0.3" "@typescript-eslint/scope-manager" "4.9.1"
"@typescript-eslint/scope-manager" "4.0.0" "@typescript-eslint/types" "4.9.1"
"@typescript-eslint/types" "4.0.0" "@typescript-eslint/typescript-estree" "4.9.1"
"@typescript-eslint/typescript-estree" "4.0.0" debug "^4.1.1"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
"@typescript-eslint/parser@^3.10.1": "@typescript-eslint/scope-manager@4.9.1":
version "3.10.1" version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz#cc2fde310b3f3deafe8436a924e784eaab265103"
integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== integrity sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==
dependencies: dependencies:
"@types/eslint-visitor-keys" "^1.0.0" "@typescript-eslint/types" "4.9.1"
"@typescript-eslint/experimental-utils" "3.10.1" "@typescript-eslint/visitor-keys" "4.9.1"
"@typescript-eslint/types" "3.10.1"
"@typescript-eslint/typescript-estree" "3.10.1"
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/scope-manager@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.0.0.tgz#8c9e3b3b8cdf5a1fbe671d9fad73ff67bc027ea8"
integrity sha512-9gcWUPoWo7gk/+ZQPg7L1ySRmR5HLIy3Vu6/LfhQbuzIkGm6v2CGIjpVRISoDLFRovNRDImd4aP/sa8O4yIEBg==
dependencies:
"@typescript-eslint/types" "4.0.0"
"@typescript-eslint/visitor-keys" "4.0.0"
"@typescript-eslint/types@3.10.1": "@typescript-eslint/types@3.10.1":
version "3.10.1" version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
"@typescript-eslint/types@4.0.0": "@typescript-eslint/types@4.9.1":
version "4.0.0" version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.0.0.tgz#ec1f9fc06b8558a1d5afa6e337182d08beece7f5" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.9.1.tgz#a1a7dd80e4e5ac2c593bc458d75dd1edaf77faa2"
integrity sha512-bK+c2VLzznX2fUWLK6pFDv3cXGTp7nHIuBMq1B9klA+QCsqLHOOqe5TQReAQDl7DN2RfH+neweo0oC5hYlG7Rg== integrity sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==
"@typescript-eslint/typescript-estree@3.10.1", "@typescript-eslint/typescript-estree@^3.6.0": "@typescript-eslint/typescript-estree@4.9.1":
version "3.10.1" version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz#6e5b86ff5a5f66809e1f347469fadeec69ac50bf"
integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== integrity sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw==
dependencies: dependencies:
"@typescript-eslint/types" "3.10.1" "@typescript-eslint/types" "4.9.1"
"@typescript-eslint/visitor-keys" "3.10.1" "@typescript-eslint/visitor-keys" "4.9.1"
debug "^4.1.1" debug "^4.1.1"
glob "^7.1.6" globby "^11.0.1"
is-glob "^4.0.1" is-glob "^4.0.1"
lodash "^4.17.15" lodash "^4.17.15"
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/typescript-estree@4.0.0": "@typescript-eslint/typescript-estree@^3.6.0":
version "4.0.0" version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.0.0.tgz#2244c63de2f2190bc5718eb0fb3fd2c437d42097" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
integrity sha512-ewFMPi2pMLDNIXGMPdf8r7El2oPSZw9PEYB0j+WcpKd7AX2ARmajGa7RUHTukllWX2bj4vWX6JLE1Oih2BMokA== integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
dependencies: dependencies:
"@typescript-eslint/types" "4.0.0" "@typescript-eslint/types" "3.10.1"
"@typescript-eslint/visitor-keys" "4.0.0" "@typescript-eslint/visitor-keys" "3.10.1"
debug "^4.1.1" debug "^4.1.1"
globby "^11.0.1" glob "^7.1.6"
is-glob "^4.0.1" is-glob "^4.0.1"
lodash "^4.17.15" lodash "^4.17.15"
semver "^7.3.2" semver "^7.3.2"
@ -2255,12 +2280,12 @@
dependencies: dependencies:
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/visitor-keys@4.0.0": "@typescript-eslint/visitor-keys@4.9.1":
version "4.0.0" version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.0.0.tgz#e2bbb69d98076d6a3f06abcb2048225a74362c33" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz#d76374a58c4ead9e92b454d186fea63487b25ae1"
integrity sha512-sTouJbv6rjVJeTE4lpSBVYXq/u5K3gbB6LKt7ccFEZPTZB/VeQ0ssUz9q5Hx++sCqBbdF8PzrrgvEnicXAR6NQ== integrity sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==
dependencies: dependencies:
"@typescript-eslint/types" "4.0.0" "@typescript-eslint/types" "4.9.1"
eslint-visitor-keys "^2.0.0" eslint-visitor-keys "^2.0.0"
"@webassemblyjs/ast@1.9.0": "@webassemblyjs/ast@1.9.0":
@ -2451,6 +2476,11 @@ acorn-jsx@^5.2.0:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
acorn-jsx@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
acorn-node@^1.6.1: acorn-node@^1.6.1:
version "1.8.2" version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
@ -2913,10 +2943,10 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==
axe-core@^3.5.4: axe-core@^4.0.2:
version "3.5.5" version "4.1.1"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf"
integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==
axios@^0.20.0: axios@^0.20.0:
version "0.20.0" version "0.20.0"
@ -2925,7 +2955,7 @@ axios@^0.20.0:
dependencies: dependencies:
follow-redirects "^1.10.0" follow-redirects "^1.10.0"
axobject-query@^2.1.2: axobject-query@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
@ -3582,7 +3612,7 @@ cheerio@^1.0.0-rc.3:
lodash "^4.15.0" lodash "^4.15.0"
parse5 "^3.0.1" parse5 "^3.0.1"
chokidar@3.4.3: chokidar@3.4.3, chokidar@^3.4.0:
version "3.4.3" version "3.4.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
@ -3956,6 +3986,11 @@ commander@^6.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc" resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc"
integrity sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA== integrity sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==
commander@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==
commitizen@^4.0.3, commitizen@^4.2.1: commitizen@^4.0.3, commitizen@^4.2.1:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.2.1.tgz#3b098b16c6b1a37f0d129018dff6751b20cd3103" resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.2.1.tgz#3b098b16c6b1a37f0d129018dff6751b20cd3103"
@ -5391,70 +5426,69 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
eslint-config-prettier@^6.11.0: eslint-config-prettier@^7.0.0:
version "6.11.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz#c1ae4106f74e6c0357f44adb076771d032ac0e97"
integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA== integrity sha512-8Y8lGLVPPZdaNA7JXqnvETVC7IiVRgAP6afQu9gOQRn90YY3otMNh+x7Vr2vMePQntF+5erdSUBqSzCmU/AxaQ==
dependencies:
get-stdin "^6.0.0"
eslint-plugin-formatjs@^2.7.10: eslint-plugin-formatjs@^2.9.10:
version "2.7.10" version "2.9.10"
resolved "https://registry.yarnpkg.com/eslint-plugin-formatjs/-/eslint-plugin-formatjs-2.7.10.tgz#436bfe8283d5108c3e93617c8bea929cbbc8e35b" resolved "https://registry.yarnpkg.com/eslint-plugin-formatjs/-/eslint-plugin-formatjs-2.9.10.tgz#dc5b80792e4166f3b2c4ca927ca47a70c89f27d2"
integrity sha512-rqhw+AgicCWDD38jluqwLKqAEuVEBI3/XcUu/AWoWrhsyg692KmBdNl0hiKaN9bv+U837q0PYXcdcFwv5TuBeQ== integrity sha512-MFkJ6ZBs70Zdyeq2JdYn950jSgSROL4x9eWlxU/AzhNvDIiHiU0oXahx02X7wdAl1vzjCC7Ro4VWiGGecQ5cpA==
dependencies: dependencies:
"@formatjs/ts-transformer" "^2.10.0" "@formatjs/ts-transformer" "2.12.10"
"@types/emoji-regex" "^8.0.0" "@types/emoji-regex" "^8.0.0"
"@types/eslint" "^7.2.0" "@types/eslint" "^7.2.0"
"@types/estree" "^0.0.45" "@types/estree" "^0.0.45"
"@typescript-eslint/typescript-estree" "^3.6.0" "@typescript-eslint/typescript-estree" "^3.6.0"
emoji-regex "^9.0.0" emoji-regex "^9.0.0"
intl-messageformat-parser "^6.0.6" intl-messageformat-parser "6.0.18"
tslib "^2.0.1"
eslint-plugin-jsx-a11y@^6.3.1: eslint-plugin-jsx-a11y@^6.4.1:
version "6.3.1" version "6.4.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd"
integrity sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g== integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==
dependencies: dependencies:
"@babel/runtime" "^7.10.2" "@babel/runtime" "^7.11.2"
aria-query "^4.2.2" aria-query "^4.2.2"
array-includes "^3.1.1" array-includes "^3.1.1"
ast-types-flow "^0.0.7" ast-types-flow "^0.0.7"
axe-core "^3.5.4" axe-core "^4.0.2"
axobject-query "^2.1.2" axobject-query "^2.2.0"
damerau-levenshtein "^1.0.6" damerau-levenshtein "^1.0.6"
emoji-regex "^9.0.0" emoji-regex "^9.0.0"
has "^1.0.3" has "^1.0.3"
jsx-ast-utils "^2.4.1" jsx-ast-utils "^3.1.0"
language-tags "^1.0.5" language-tags "^1.0.5"
eslint-plugin-prettier@^3.1.4: eslint-plugin-prettier@^3.2.0:
version "3.1.4" version "3.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.2.0.tgz#af391b2226fa0e15c96f36c733f6e9035dbd952c"
integrity sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg== integrity sha512-kOUSJnFjAUFKwVxuzy6sA5yyMx6+o9ino4gCdShzBNx4eyFRudWRYKCFolKjoM40PEiuU6Cn7wBLfq3WsGg7qg==
dependencies: dependencies:
prettier-linter-helpers "^1.0.0" prettier-linter-helpers "^1.0.0"
eslint-plugin-react-hooks@^4.1.2: eslint-plugin-react-hooks@^4.2.0:
version "4.1.2" version "4.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.2.tgz#2eb53731d11c95826ef7a7272303eabb5c9a271e" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
integrity sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg== integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==
eslint-plugin-react@^7.20.6: eslint-plugin-react@^7.21.5:
version "7.20.6" version "7.21.5"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz#4d7845311a93c463493ccfa0a19c9c5d0fd69f60" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3"
integrity sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg== integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==
dependencies: dependencies:
array-includes "^3.1.1" array-includes "^3.1.1"
array.prototype.flatmap "^1.2.3" array.prototype.flatmap "^1.2.3"
doctrine "^2.1.0" doctrine "^2.1.0"
has "^1.0.3" has "^1.0.3"
jsx-ast-utils "^2.4.1" jsx-ast-utils "^2.4.1 || ^3.0.0"
object.entries "^1.1.2" object.entries "^1.1.2"
object.fromentries "^2.0.2" object.fromentries "^2.0.2"
object.values "^1.1.1" object.values "^1.1.1"
prop-types "^15.7.2" prop-types "^15.7.2"
resolve "^1.17.0" resolve "^1.18.1"
string.prototype.matchall "^4.0.2" string.prototype.matchall "^4.0.2"
eslint-scope@^4.0.3: eslint-scope@^4.0.3:
@ -5490,13 +5524,13 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
eslint@^7.10.0: eslint@^7.15.0:
version "7.10.0" version "7.15.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.10.0.tgz#494edb3e4750fb791133ca379e786a8f648c72b9" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.15.0.tgz#eb155fb8ed0865fcf5d903f76be2e5b6cd7e0bc7"
integrity sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA== integrity sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
"@eslint/eslintrc" "^0.1.3" "@eslint/eslintrc" "^0.2.2"
ajv "^6.10.0" ajv "^6.10.0"
chalk "^4.0.0" chalk "^4.0.0"
cross-spawn "^7.0.2" cross-spawn "^7.0.2"
@ -5505,11 +5539,11 @@ eslint@^7.10.0:
enquirer "^2.3.5" enquirer "^2.3.5"
eslint-scope "^5.1.1" eslint-scope "^5.1.1"
eslint-utils "^2.1.0" eslint-utils "^2.1.0"
eslint-visitor-keys "^1.3.0" eslint-visitor-keys "^2.0.0"
espree "^7.3.0" espree "^7.3.1"
esquery "^1.2.0" esquery "^1.2.0"
esutils "^2.0.2" esutils "^2.0.2"
file-entry-cache "^5.0.1" file-entry-cache "^6.0.0"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
glob-parent "^5.0.0" glob-parent "^5.0.0"
globals "^12.1.0" globals "^12.1.0"
@ -5542,6 +5576,15 @@ espree@^7.3.0:
acorn-jsx "^5.2.0" acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.3.0" eslint-visitor-keys "^1.3.0"
espree@^7.3.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
dependencies:
acorn "^7.4.0"
acorn-jsx "^5.3.1"
eslint-visitor-keys "^1.3.0"
esprima@^4.0.0, esprima@~4.0.0: esprima@^4.0.0, esprima@~4.0.0:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@ -5632,7 +5675,7 @@ execa@^0.7.0:
signal-exit "^3.0.0" signal-exit "^3.0.0"
strip-eof "^1.0.0" strip-eof "^1.0.0"
execa@^4.0.0: execa@^4.0.0, execa@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
@ -5647,21 +5690,6 @@ execa@^4.0.0:
signal-exit "^3.0.2" signal-exit "^3.0.2"
strip-final-newline "^2.0.0" strip-final-newline "^2.0.0"
execa@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
dependencies:
cross-spawn "^7.0.0"
get-stream "^5.0.0"
human-signals "^1.1.1"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.0"
onetime "^5.1.0"
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
expand-brackets@^2.1.4: expand-brackets@^2.1.4:
version "2.1.4" version "2.1.4"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
@ -5915,12 +5943,12 @@ figures@^3.0.0, figures@^3.2.0:
dependencies: dependencies:
escape-string-regexp "^1.0.5" escape-string-regexp "^1.0.5"
file-entry-cache@^5.0.1: file-entry-cache@^6.0.0:
version "5.0.1" version "6.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a"
integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==
dependencies: dependencies:
flat-cache "^2.0.1" flat-cache "^3.0.4"
file-uri-to-path@1.0.0: file-uri-to-path@1.0.0:
version "1.0.0" version "1.0.0"
@ -6040,24 +6068,23 @@ findup-sync@^3.0.0:
micromatch "^3.0.4" micromatch "^3.0.4"
resolve-dir "^1.0.1" resolve-dir "^1.0.1"
flat-cache@^2.0.1: flat-cache@^3.0.4:
version "2.0.1" version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
dependencies: dependencies:
flatted "^2.0.0" flatted "^3.1.0"
rimraf "2.6.3" rimraf "^3.0.2"
write "1.0.3"
flat@^5.0.0: flat@^5.0.0:
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
flatted@^2.0.0: flatted@^3.1.0:
version "2.0.2" version "3.1.0"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067"
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==
flatten@^1.0.2: flatten@^1.0.2:
version "1.0.3" version "1.0.3"
@ -6335,11 +6362,6 @@ get-stdin@8.0.0:
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
get-stream@^3.0.0: get-stream@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@ -6846,10 +6868,10 @@ humanize-ms@^1.2.1:
dependencies: dependencies:
ms "^2.0.0" ms "^2.0.0"
husky@^4.3.0: husky@^4.3.5:
version "4.3.0" version "4.3.5"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.5.tgz#ab8d2a0eb6b62fef2853ee3d442c927d89290902"
integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== integrity sha512-E5S/1HMoDDaqsH8kDF5zeKEQbYqe3wL9zJDyqyYqc8I4vHBtAoxkDBGXox0lZ9RI+k5GyB728vZdmnM4bYap+g==
dependencies: dependencies:
chalk "^4.0.0" chalk "^4.0.0"
ci-info "^2.0.0" ci-info "^2.0.0"
@ -7061,6 +7083,14 @@ internal-slot@^1.0.2:
has "^1.0.3" has "^1.0.3"
side-channel "^1.0.2" side-channel "^1.0.2"
intl-messageformat-parser@6.0.18:
version "6.0.18"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.0.18.tgz#bf2855b82b0749e1f34e452f0a15d08d3277c8c7"
integrity sha512-vLjACEunfi5uSUCWFLOR4PXQ9DGLpED3tM7o9zsYsOvjl0VIheoxyG0WZXnsnhn+S+Zu158M6CkuHXeNZfKRRg==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
tslib "^2.0.1"
intl-messageformat-parser@^5.3.7: intl-messageformat-parser@^5.3.7:
version "5.5.1" version "5.5.1"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-5.5.1.tgz#f09a692755813e6220081e3374df3fb1698bd0c6" resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-5.5.1.tgz#f09a692755813e6220081e3374df3fb1698bd0c6"
@ -7675,13 +7705,13 @@ jstransformer@1.0.0:
is-promise "^2.0.0" is-promise "^2.0.0"
promise "^7.0.1" promise "^7.0.1"
jsx-ast-utils@^2.4.1: "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0:
version "2.4.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891"
integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==
dependencies: dependencies:
array-includes "^3.1.1" array-includes "^3.1.1"
object.assign "^4.1.0" object.assign "^4.1.1"
juice@^7.0.0: juice@^7.0.0:
version "7.0.0" version "7.0.0"
@ -7994,20 +8024,20 @@ linkify-it@3.0.2:
dependencies: dependencies:
uc.micro "^1.0.1" uc.micro "^1.0.1"
lint-staged@^10.4.0: lint-staged@^10.5.3:
version "10.4.0" version "10.5.3"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.4.0.tgz#d18628f737328e0bbbf87d183f4020930e9a984e" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.3.tgz#c682838b3eadd4c864d1022da05daa0912fb1da5"
integrity sha512-uaiX4U5yERUSiIEQc329vhCTDDwUcSvKdRLsNomkYLRzijk3v8V9GWm2Nz0RMVB87VcuzLvtgy6OsjoH++QHIg== integrity sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg==
dependencies: dependencies:
chalk "^4.1.0" chalk "^4.1.0"
cli-truncate "^2.1.0" cli-truncate "^2.1.0"
commander "^6.0.0" commander "^6.2.0"
cosmiconfig "^7.0.0" cosmiconfig "^7.0.0"
debug "^4.1.1" debug "^4.2.0"
dedent "^0.7.0" dedent "^0.7.0"
enquirer "^2.3.6" enquirer "^2.3.6"
execa "^4.0.3" execa "^4.1.0"
listr2 "^2.6.0" listr2 "^3.2.2"
log-symbols "^4.0.0" log-symbols "^4.0.0"
micromatch "^4.0.2" micromatch "^4.0.2"
normalize-path "^3.0.0" normalize-path "^3.0.0"
@ -8015,10 +8045,10 @@ lint-staged@^10.4.0:
string-argv "0.3.1" string-argv "0.3.1"
stringify-object "^3.3.0" stringify-object "^3.3.0"
listr2@^2.6.0: listr2@^3.2.2:
version "2.6.2" version "3.2.3"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.6.2.tgz#4912eb01e1e2dd72ec37f3895a56bf2622d6f36a" resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.2.3.tgz#ef9e0d790862f038dde8a9837be552b1adfd1c07"
integrity sha512-6x6pKEMs8DSIpA/tixiYY2m/GcbgMplMVmhQAaLFxEtNSKLeWTGjtmU57xvv6QCm2XcqzyNXL/cTSVf4IChCRA== integrity sha512-vUb80S2dSUi8YxXahO8/I/s29GqnOL8ozgHVLjfWQXa03BNEeS1TpBLjh2ruaqq5ufx46BRGvfymdBSuoXET5w==
dependencies: dependencies:
chalk "^4.1.0" chalk "^4.1.0"
cli-truncate "^2.1.0" cli-truncate "^2.1.0"
@ -8026,7 +8056,7 @@ listr2@^2.6.0:
indent-string "^4.0.0" indent-string "^4.0.0"
log-update "^4.0.0" log-update "^4.0.0"
p-map "^4.0.0" p-map "^4.0.0"
rxjs "^6.6.2" rxjs "^6.6.3"
through "^2.3.8" through "^2.3.8"
load-json-file@^2.0.0: load-json-file@^2.0.0:
@ -9221,10 +9251,10 @@ nodemailer@^6.4.16:
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.16.tgz#5cb6391b1d79ab7eff32d6f9f48366b5a7117293" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.16.tgz#5cb6391b1d79ab7eff32d6f9f48366b5a7117293"
integrity sha512-68K0LgZ6hmZ7PVmwL78gzNdjpj5viqBdFqKrTtr9bZbJYj6BRj5W6WGkxXrEnUl3Co3CBXi3CZBUlpV/foGnOQ== integrity sha512-68K0LgZ6hmZ7PVmwL78gzNdjpj5viqBdFqKrTtr9bZbJYj6BRj5W6WGkxXrEnUl3Co3CBXi3CZBUlpV/foGnOQ==
nodemon@^2.0.4: nodemon@^2.0.6:
version "2.0.4" version "2.0.6"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.6.tgz#1abe1937b463aaf62f0d52e2b7eaadf28cc2240d"
integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== integrity sha512-4I3YDSKXg6ltYpcnZeHompqac4E6JeAMpGm8tJnB9Y3T0ehasLa4139dJOcCrB93HHrUMsCrKtoAlXTqT5n4AQ==
dependencies: dependencies:
chokidar "^3.2.2" chokidar "^3.2.2"
debug "^3.2.6" debug "^3.2.6"
@ -9234,8 +9264,8 @@ nodemon@^2.0.4:
semver "^5.7.1" semver "^5.7.1"
supports-color "^5.5.0" supports-color "^5.5.0"
touch "^3.1.0" touch "^3.1.0"
undefsafe "^2.0.2" undefsafe "^2.0.3"
update-notifier "^4.0.0" update-notifier "^4.1.0"
noms@0.0.0: noms@0.0.0:
version "0.0.0" version "0.0.0"
@ -11160,6 +11190,11 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-intersection-observer@^8.31.0:
version "8.31.0"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.31.0.tgz#0ed21aaf93c4c0475b22b0ccaba6169076d01605"
integrity sha512-XraIC/tkrD9JtrmVA7ypEN1QIpKc52mXBH1u/bz/aicRLo8QQEJQAMUTb8mz4B6dqpPwyzgjrr7Ljv/2ACDtqw==
react-intl@^5.8.5: react-intl@^5.8.5:
version "5.8.5" version "5.8.5"
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.5.tgz#bc5dfab259049830621e129b8bffb1ac33ef4124" resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.5.tgz#bc5dfab259049830621e129b8bffb1ac33ef4124"
@ -11653,7 +11688,7 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.17.0, resolve@^1.3.2:
dependencies: dependencies:
path-parse "^1.0.6" path-parse "^1.0.6"
resolve@^1.15.1, resolve@^1.19.0: resolve@^1.15.1, resolve@^1.18.1, resolve@^1.19.0:
version "1.19.0" version "1.19.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
@ -11724,13 +11759,6 @@ rimraf@2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rimraf@2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
dependencies:
glob "^7.1.3"
rimraf@^3.0.2: rimraf@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@ -11763,13 +11791,20 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies: dependencies:
aproba "^1.1.1" aproba "^1.1.1"
rxjs@^6.4.0, rxjs@^6.6.2: rxjs@^6.4.0:
version "6.6.2" version "6.6.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2"
integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
rxjs@^6.6.3:
version "6.6.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@ -13361,7 +13396,7 @@ umask@^1.1.0, umask@~1.1.0:
resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
undefsafe@^2.0.2: undefsafe@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae"
integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==
@ -13508,10 +13543,10 @@ update-notifier@^2.2.0, update-notifier@^2.3.0, update-notifier@^2.5.0:
semver-diff "^2.0.0" semver-diff "^2.0.0"
xdg-basedir "^3.0.0" xdg-basedir "^3.0.0"
update-notifier@^4.0.0: update-notifier@^4.1.0:
version "4.1.0" version "4.1.3"
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3"
integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==
dependencies: dependencies:
boxen "^4.2.0" boxen "^4.2.0"
chalk "^3.0.0" chalk "^3.0.0"
@ -13634,11 +13669,6 @@ uuid@^3.3.2, uuid@^3.3.3:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
uuid@^8.3.1: uuid@^8.3.1:
version "8.3.1" version "8.3.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
@ -13946,13 +13976,6 @@ write-json-file@^4.3.0:
sort-keys "^4.0.0" sort-keys "^4.0.0"
write-file-atomic "^3.0.0" write-file-atomic "^3.0.0"
write@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
dependencies:
mkdirp "^0.5.1"
xdg-basedir@^3.0.0: xdg-basedir@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"

Loading…
Cancel
Save