diff --git a/components/AuditPage/AuditPage.tsx b/components/AuditPage/AuditPage.tsx
index 3d8c10b..56f738f 100644
--- a/components/AuditPage/AuditPage.tsx
+++ b/components/AuditPage/AuditPage.tsx
@@ -1,3 +1,4 @@
+import Head from 'next/head';
import { useEffect, useState } from 'react';
import useSWR from 'swr';
import useUser from '../../lib/hooks/useUser';
@@ -168,6 +169,9 @@ export const AuditPage = () => {
return (
<>
+
+ Audit logs | Icy Network Administration
+
Audit logs
diff --git a/components/LoginPage/LoginPage.tsx b/components/LoginPage/LoginPage.tsx
index 808062b..81de291 100644
--- a/components/LoginPage/LoginPage.tsx
+++ b/components/LoginPage/LoginPage.tsx
@@ -1,15 +1,21 @@
+import Head from 'next/head';
import Link from 'next/link';
import styles from './LoginPage.module.scss';
export const LoginPage = () => {
return (
-
-
-
+ <>
+
+ Login | Icy Network Administration
+
+
+
+
+ >
);
};
diff --git a/components/OAuth2Page/OAuth2Page.module.scss b/components/OAuth2Page/OAuth2Page.module.scss
index def59ab..2e4afa9 100644
--- a/components/OAuth2Page/OAuth2Page.module.scss
+++ b/components/OAuth2Page/OAuth2Page.module.scss
@@ -52,3 +52,16 @@
flex-direction: column;
gap: 1rem;
}
+
+.urlWrapper {
+ display: flex;
+ gap: 0.5rem;
+
+ :first-child {
+ flex-grow: 1;
+ }
+
+ button {
+ min-width: 4.625rem;
+ }
+}
diff --git a/components/OAuth2Page/OAuth2Page.tsx b/components/OAuth2Page/OAuth2Page.tsx
index 4ac2989..65233e5 100644
--- a/components/OAuth2Page/OAuth2Page.tsx
+++ b/components/OAuth2Page/OAuth2Page.tsx
@@ -1,4 +1,4 @@
-import useSWR, { mutate } from 'swr';
+import useSWR from 'swr';
import Image from 'next/image';
import {
OAuth2ClientListItem,
@@ -10,7 +10,7 @@ import { Paginator } from '../common/Paginator/Paginator';
import styles from './OAuth2Page.module.scss';
import { UPLOADS_URL } from '../../lib/constants';
import application from '../../public/application.png';
-import { ChangeEvent, useMemo, useRef, useState } from 'react';
+import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
import useUser from '../../lib/hooks/useUser';
import { Container } from '../common/Container/Container';
import { Header } from '../common/Header/Header';
@@ -28,51 +28,141 @@ import toast from 'react-hot-toast';
import { Button } from '../common/Button/Button';
import userHasPrivileges from '../../lib/utils/has-privileges';
import { publishJSON } from '../../lib/utils/fetch';
+import Head from 'next/head';
-const LINK_NAMES = {
+const LINK_NAMES: Record = {
redirect_uri: 'Redirect URI',
terms: 'Terms of Service',
privacy: 'Privacy Policy',
website: 'Website',
};
+const REDIRECT_URI_COUNT = 3;
+
const LinkEdit = ({
+ link,
+ onChange,
+ onRemove,
+}: {
+ link: Partial;
+ onChange: () => void;
+ onRemove: () => void;
+}) => {
+ return (
+
+
+
+ {
+ link.url = e.target.value;
+ onChange();
+ }}
+ />
+
+
+ {link.type === OAuth2ClientURLType.REDIRECT_URI && (
+ Wildcards are NOT allowed!
+ )}
+
+ );
+};
+
+const LinkEditor = ({
formData,
handleInputChange,
- linkType,
}: {
formData: Partial;
handleInputChange: (
- e: ChangeEvent,
+ e?: ChangeEvent,
setValue?: any,
formField?: string
) => void;
- linkType: OAuth2ClientURLType;
}) => {
- const formUrl = useMemo>(
- () => (formData.urls || []).find(({ type }) => type === linkType) || {},
- [formData, linkType]
+ const [links, setLinks] = useState[]>(
+ formData.urls || []
);
+ const [addNewSelection, setAddNewSelection] = useState();
+ const availableTypes = useMemo(
+ () =>
+ Object.values(OAuth2ClientURLType).filter((type) =>
+ type === 'redirect_uri'
+ ? links.filter(
+ (link) => link.type === OAuth2ClientURLType.REDIRECT_URI
+ ).length < REDIRECT_URI_COUNT
+ : !links.some((link) => link.type === type)
+ ),
+ [links]
+ );
+
+ useEffect(() => {
+ if (
+ (!addNewSelection ||
+ availableTypes.indexOf(addNewSelection as OAuth2ClientURLType) ===
+ -1) &&
+ availableTypes.length
+ ) {
+ setAddNewSelection(availableTypes[0]);
+ }
+ }, [availableTypes, addNewSelection]);
+
return (
-
-
- {
- if (!formUrl.type) {
- formUrl.type = linkType;
- (formData.urls as Partial[]) = [
- ...(formData.urls || []),
- formUrl,
- ];
- }
- formUrl.url = e.target.value;
- handleInputChange(e, formData.urls, 'urls');
- }}
- />
-
+ <>
+ Client URLs
+ {links.map((link, index) => (
+ {
+ setLinks(links);
+ handleInputChange(undefined, links, 'urls');
+ }}
+ onRemove={() => {
+ const clone = links.slice();
+ clone.splice(index, 1);
+ setLinks(clone);
+ handleInputChange(undefined, clone, 'urls');
+ }}
+ />
+ ))}
+
+ {availableTypes.length > 0 && (
+
+
+
+
+ )}
+
+ >
);
};
@@ -207,26 +297,10 @@ const EditClientModal = ({
>
)}
-
-
-
-
+ >
@@ -392,6 +466,7 @@ const OAuth2ClientCard = ({
const OAuth2ClientList = ({ isAdmin }: { isAdmin: boolean }) => {
const [pageIndex, setPageIndex] = useState(1);
+ const [clientCount, setClientCount] = useState();
const [searchTerm, setSearchTerm] = useState('');
const { data, mutate } = useSWR>(
`/api/admin/oauth2/clients?page=${pageIndex}${
@@ -399,10 +474,16 @@ const OAuth2ClientList = ({ isAdmin }: { isAdmin: boolean }) => {
}&pageSize=8`
);
+ useEffect(() => {
+ if (data?.pagination?.rowCount) {
+ setClientCount(data?.pagination.rowCount);
+ }
+ }, [data]);
+
return (
<>
-
OAuth2 clients
+
OAuth2 clients{clientCount && ` (${clientCount})`}