feat(dashboard-v2): proper copies about api keys
This commit is contained in:
parent
53b0687b21
commit
efed2045af
|
@ -13,7 +13,7 @@ import { useAPIKeyRemoval } from "./useAPIKeyRemoval";
|
||||||
|
|
||||||
export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
||||||
const { id, name, createdAt, skylinks } = apiKey;
|
const { id, name, createdAt, skylinks } = apiKey;
|
||||||
const isPublic = apiKey.public === "true";
|
const isSponsorKey = apiKey.public === "true";
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
const onSkylinkListEdited = useCallback(() => {
|
const onSkylinkListEdited = useCallback(() => {
|
||||||
|
@ -53,9 +53,9 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
||||||
}, [abortEdit]);
|
}, [abortEdit]);
|
||||||
|
|
||||||
const skylinksNumber = skylinks?.length ?? 0;
|
const skylinksNumber = skylinks?.length ?? 0;
|
||||||
const isNotConfigured = isPublic && skylinksNumber === 0;
|
const isNotConfigured = isSponsorKey && skylinksNumber === 0;
|
||||||
const skylinksPhrasePrefix = skylinksNumber === 0 ? "No" : skylinksNumber;
|
const skylinksPhrasePrefix = skylinksNumber === 0 ? "No" : skylinksNumber;
|
||||||
const skylinksPhrase = `${skylinksPhrasePrefix} ${skylinksNumber === 1 ? "skylink" : "skylinks"} configured`;
|
const skylinksPhrase = `${skylinksPhrasePrefix} ${skylinksNumber === 1 ? "skylink" : "skylinks"} sponsored`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
|
@ -66,21 +66,23 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
||||||
<span className="col-span-2 sm:col-span-1 flex items-center">
|
<span className="col-span-2 sm:col-span-1 flex items-center">
|
||||||
<span className="flex flex-col">
|
<span className="flex flex-col">
|
||||||
<span className={cn("truncate", { "text-palette-300": !name })}>{name || "unnamed key"}</span>
|
<span className={cn("truncate", { "text-palette-300": !name })}>{name || "unnamed key"}</span>
|
||||||
<button
|
{isSponsorKey && (
|
||||||
onClick={promptEdit}
|
<button
|
||||||
className={cn("text-xs hover:underline decoration-dotted", {
|
onClick={promptEdit}
|
||||||
"text-error": isNotConfigured,
|
className={cn("text-xs hover:underline decoration-dotted", {
|
||||||
"text-palette-400": !isNotConfigured,
|
"text-error": isNotConfigured,
|
||||||
})}
|
"text-palette-400": !isNotConfigured,
|
||||||
>
|
})}
|
||||||
{skylinksPhrase}
|
>
|
||||||
</button>
|
{skylinksPhrase}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="col-span-2 my-4 border-t border-t-palette-200/50 sm:hidden" />
|
<span className="col-span-2 my-4 border-t border-t-palette-200/50 sm:hidden" />
|
||||||
<span className="text-palette-400">{dayjs(createdAt).format("MMM DD, YYYY")}</span>
|
<span className="text-palette-400">{dayjs(createdAt).format("MMM DD, YYYY")}</span>
|
||||||
<span className="flex items-center justify-end">
|
<span className="flex items-center justify-end">
|
||||||
{isPublic && (
|
{isSponsorKey && (
|
||||||
<button
|
<button
|
||||||
title="Add or remove skylinks"
|
title="Add or remove skylinks"
|
||||||
aria-label="Add or remove skylinks"
|
aria-label="Add or remove skylinks"
|
||||||
|
@ -119,7 +121,7 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
||||||
)}
|
)}
|
||||||
{editInitiated && (
|
{editInitiated && (
|
||||||
<Modal onClose={closeEditModal} className="flex flex-col gap-4 text-center sm:px-8 sm:py-6">
|
<Modal onClose={closeEditModal} className="flex flex-col gap-4 text-center sm:px-8 sm:py-6">
|
||||||
<h4>Covered skylinks</h4>
|
<h4>Sponsored skylinks</h4>
|
||||||
{skylinks?.length > 0 ? (
|
{skylinks?.length > 0 ? (
|
||||||
<ul className="text-xs flex flex-col gap-2">
|
<ul className="text-xs flex flex-col gap-2">
|
||||||
{skylinks.map((skylink) => (
|
{skylinks.map((skylink) => (
|
||||||
|
|
|
@ -2,5 +2,5 @@ import { Link } from "gatsby";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
export default styled(Link).attrs({
|
export default styled(Link).attrs({
|
||||||
className: "text-primary underline-offset-3 decoration-dotted hover:text-primary-light hover:underline",
|
className: "text-primary underline-offset-2 decoration-1 decoration-dotted hover:text-primary-light hover:underline",
|
||||||
})``;
|
})``;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./Tooltip";
|
|
@ -2,6 +2,7 @@ import * as Yup from "yup";
|
||||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { Formik, Form } from "formik";
|
import { Formik, Form } from "formik";
|
||||||
|
import cn from "classnames";
|
||||||
|
|
||||||
import accountsService from "../../services/accountsService";
|
import accountsService from "../../services/accountsService";
|
||||||
|
|
||||||
|
@ -9,7 +10,6 @@ import { Alert } from "../Alert";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
import { CopyButton } from "../CopyButton";
|
import { CopyButton } from "../CopyButton";
|
||||||
import { TextField } from "../Form/TextField";
|
import { TextField } from "../Form/TextField";
|
||||||
import { CircledProgressIcon, PlusIcon } from "../Icons";
|
|
||||||
|
|
||||||
const newAPIKeySchema = Yup.object().shape({
|
const newAPIKeySchema = Yup.object().shape({
|
||||||
name: Yup.string(),
|
name: Yup.string(),
|
||||||
|
@ -22,7 +22,7 @@ const State = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const APIKeyType = {
|
export const APIKeyType = {
|
||||||
Public: "public",
|
Sponsor: "sponsor",
|
||||||
General: "general",
|
General: "general",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,10 +37,10 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="flex flex-col gap-4">
|
<div ref={ref} className="flex flex-col gap-4">
|
||||||
{state === State.Success && (
|
{state === State.Success && (
|
||||||
<Alert $variant="success" className="text-center">
|
<Alert $variant="success">
|
||||||
<strong>Success!</strong>
|
<strong>Success!</strong>
|
||||||
<p>Please copy your new API key below. We'll never show it again!</p>
|
<p>Please copy your new API key below. We'll never show it again!</p>
|
||||||
<div className="flex items-center gap-2 mt-4 justify-center">
|
<div className="flex items-center gap-2 mt-4">
|
||||||
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
||||||
{generatedKey}
|
{generatedKey}
|
||||||
</code>
|
</code>
|
||||||
|
@ -62,8 +62,8 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
||||||
.post("user/apikeys", {
|
.post("user/apikeys", {
|
||||||
json: {
|
json: {
|
||||||
name,
|
name,
|
||||||
public: type === APIKeyType.Public ? "true" : "false",
|
public: type === APIKeyType.Sponsor ? "true" : "false",
|
||||||
skylinks: type === APIKeyType.Public ? [] : null,
|
skylinks: type === APIKeyType.Sponsor ? [] : null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
|
@ -78,26 +78,20 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ errors, touched, isSubmitting }) => (
|
{({ errors, touched, isSubmitting }) => (
|
||||||
<Form className="grid grid-cols-[1fr_min-content] w-full gap-y-2 gap-x-4 items-start">
|
<Form className="flex flex-col gap-4">
|
||||||
<div className="flex items-center">
|
<TextField
|
||||||
<TextField
|
type="text"
|
||||||
type="text"
|
id="name"
|
||||||
id="name"
|
name="name"
|
||||||
name="name"
|
label="New API Key Label"
|
||||||
label="New API Key Name"
|
placeholder="my_applications_statistics"
|
||||||
placeholder="my_applications_statistics"
|
error={errors.name}
|
||||||
error={errors.name}
|
touched={touched.name}
|
||||||
touched={touched.name}
|
/>
|
||||||
/>
|
<div className="flex justify-center">
|
||||||
</div>
|
<Button type="submit" disabled={isSubmitting} className={cn({ "cursor-wait": isSubmitting })}>
|
||||||
<div className="flex mt-5 justify-center">
|
{isSubmitting ? "Generating your API key..." : "Generate your API key"}
|
||||||
{isSubmitting ? (
|
</Button>
|
||||||
<CircledProgressIcon size={38} className="text-palette-300 animate-[spin_3s_linear_infinite]" />
|
|
||||||
) : (
|
|
||||||
<Button type="submit" className="px-2.5" aria-label="Create general API key">
|
|
||||||
<PlusIcon size={14} />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
@ -110,5 +104,5 @@ AddAPIKeyForm.displayName = "AddAPIKeyForm";
|
||||||
|
|
||||||
AddAPIKeyForm.propTypes = {
|
AddAPIKeyForm.propTypes = {
|
||||||
onSuccess: PropTypes.func.isRequired,
|
onSuccess: PropTypes.func.isRequired,
|
||||||
type: PropTypes.oneOf([APIKeyType.Public, APIKeyType.General]).isRequired,
|
type: PropTypes.oneOf([APIKeyType.Sponsor, APIKeyType.General]).isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,10 +52,10 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="flex flex-col gap-4">
|
<div ref={ref} className="flex flex-col gap-4">
|
||||||
{state === State.Success && (
|
{state === State.Success && (
|
||||||
<Alert $variant="success" className="text-center">
|
<Alert $variant="success">
|
||||||
<strong>Success!</strong>
|
<strong>Success!</strong>
|
||||||
<p>Please copy your new API key below. We'll never show it again!</p>
|
<p>Please copy your new API key below. We'll never show it again!</p>
|
||||||
<div className="flex items-center gap-2 mt-4 justify-center">
|
<div className="flex items-center gap-2 mt-4">
|
||||||
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
||||||
{generatedKey}
|
{generatedKey}
|
||||||
</code>
|
</code>
|
||||||
|
@ -101,14 +101,14 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
label="Public API Key Name"
|
label="Sponsor API Key Name"
|
||||||
placeholder="my_applications_statistics"
|
placeholder="my_applications_statistics"
|
||||||
error={errors.name}
|
error={errors.name}
|
||||||
touched={touched.name}
|
touched={touched.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 className="text-palette-300 mb-2">Skylinks accessible with the new key</h6>
|
<h6 className="text-palette-300 mb-2">Skylinks sponsored by the new key</h6>
|
||||||
<FieldArray
|
<FieldArray
|
||||||
name="skylinks"
|
name="skylinks"
|
||||||
render={({ push, remove }) => {
|
render={({ push, remove }) => {
|
||||||
|
@ -182,7 +182,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
className={cn("px-2.5", { "cursor-wait": isSubmitting })}
|
className={cn("px-2.5", { "cursor-wait": isSubmitting })}
|
||||||
disabled={!isValid || isSubmitting}
|
disabled={!isValid || isSubmitting}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "Generating" : "Generate"} your public key
|
{isSubmitting ? "Generating your sponsor key..." : "Generate your sponsor key"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -192,7 +192,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddPublicAPIKeyForm.displayName = "AddAPIKeyForm";
|
AddPublicAPIKeyForm.displayName = "AddPublicAPIKeyForm";
|
||||||
|
|
||||||
AddPublicAPIKeyForm.propTypes = {
|
AddPublicAPIKeyForm.propTypes = {
|
||||||
onSuccess: PropTypes.func.isRequired,
|
onSuccess: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -16,8 +16,8 @@ const Sidebar = () => (
|
||||||
<SidebarLink activeClassName="!border-l-primary" to="/settings/export">
|
<SidebarLink activeClassName="!border-l-primary" to="/settings/export">
|
||||||
Export
|
Export
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink activeClassName="!border-l-primary" to="/settings/api-keys">
|
<SidebarLink activeClassName="!border-l-primary" to="/settings/developer-settings">
|
||||||
API Keys
|
Developer settings
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
@ -8,11 +8,12 @@ import { APIKeyList } from "../../components/APIKeyList/APIKeyList";
|
||||||
import { Alert } from "../../components/Alert";
|
import { Alert } from "../../components/Alert";
|
||||||
import { AddPublicAPIKeyForm } from "../../components/forms/AddPublicAPIKeyForm";
|
import { AddPublicAPIKeyForm } from "../../components/forms/AddPublicAPIKeyForm";
|
||||||
import { Metadata } from "../../components/Metadata";
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
import HighlightedLink from "../../components/HighlightedLink";
|
||||||
|
|
||||||
const APIKeysPage = () => {
|
const DeveloperSettingsPage = () => {
|
||||||
const { data: apiKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
const { data: allKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
||||||
const generalKeys = apiKeys.filter(({ public: isPublic }) => isPublic === "false");
|
const apiKeys = allKeys.filter(({ public: isPublic }) => isPublic === "false");
|
||||||
const publicKeys = apiKeys.filter(({ public: isPublic }) => isPublic === "true");
|
const sponsorKeys = allKeys.filter(({ public: isPublic }) => isPublic === "true");
|
||||||
|
|
||||||
const publicFormRef = useRef();
|
const publicFormRef = useRef();
|
||||||
const generalFormRef = useRef();
|
const generalFormRef = useRef();
|
||||||
|
@ -31,53 +32,60 @@ const APIKeysPage = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Metadata>
|
<Metadata>
|
||||||
<title>API Keys</title>
|
<title>Developer settings</title>
|
||||||
</Metadata>
|
</Metadata>
|
||||||
<div className="flex flex-col xl:flex-row">
|
<div className="flex flex-col xl:flex-row">
|
||||||
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px] leading-relaxed">
|
||||||
<div>
|
<div>
|
||||||
<h4>API Keys</h4>
|
<h4>Developer settings</h4>
|
||||||
<p className="leading-relaxed">There are two types of API keys that you can generate for your account.</p>
|
<p>API keys allow developers and applications to extend the functionality of your portal account.</p>
|
||||||
<p>Make sure to use the appropriate type.</p>
|
<p>Skynet uses two types of API keys, explained below.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<section className="flex flex-col gap-2">
|
<section className="flex flex-col gap-2">
|
||||||
<h5>Public keys</h5>
|
<h5>Sponsor keys</h5>
|
||||||
<p className="text-palette-500">
|
<div className="text-palette-500"></div>
|
||||||
Public keys provide read access to a selected list of skylinks. You can share them publicly.
|
<p>
|
||||||
|
Sponsor keys allow users without an account on this portal to download skylinks covered by the API key.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Learn more about sponsoring content with Sponsor API Keys{" "}
|
||||||
|
<HighlightedLink as="a" href="#">
|
||||||
|
here
|
||||||
|
</HighlightedLink>
|
||||||
|
.
|
||||||
|
</p>{" "}
|
||||||
|
{/* TODO: missing documentation link */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<AddPublicAPIKeyForm ref={publicFormRef} onSuccess={refreshState} />
|
<AddPublicAPIKeyForm ref={publicFormRef} onSuccess={refreshState} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<Alert $variant="error" className="mt-4">
|
<Alert $variant="error" className="mt-4">
|
||||||
An error occurred while loading your API keys. Please try again later.
|
An error occurred while loading your sponsor keys. Please try again later.
|
||||||
</Alert>
|
</Alert>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
{publicKeys?.length > 0 ? (
|
{sponsorKeys?.length > 0 ? (
|
||||||
<APIKeyList title="Your public keys" keys={publicKeys} reloadKeys={() => refreshState(true)} />
|
<APIKeyList title="Your public keys" keys={sponsorKeys} reloadKeys={() => refreshState(true)} />
|
||||||
) : (
|
) : (
|
||||||
<Alert $variant="info">No public API keys found.</Alert>
|
<Alert $variant="info">No sponsor keys found.</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<section className="flex flex-col gap-2">
|
<section className="flex flex-col gap-2">
|
||||||
<h5>General keys</h5>
|
<h5>API keys</h5>
|
||||||
<p className="text-palette-500">
|
<p className="text-palette-500">
|
||||||
These keys provide full access to <b>Accounts</b> service and are equivalent to using a JWT token.
|
These keys provide full access to <b>Accounts</b> service and are equivalent to using a JWT token.
|
||||||
</p>
|
</p>
|
||||||
<p className="underline">
|
<p className="font-bold">
|
||||||
This type of API keys needs to be kept secret and should never be shared with anyone.
|
This type of API keys needs to be kept secret and should never be shared with anyone.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<AddAPIKeyForm ref={generalFormRef} onSuccess={refreshState} type={APIKeyType.General} />
|
<AddAPIKeyForm ref={generalFormRef} onSuccess={refreshState} type={APIKeyType.General} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,10 +96,10 @@ const APIKeysPage = () => {
|
||||||
</Alert>
|
</Alert>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
{generalKeys?.length > 0 ? (
|
{apiKeys?.length > 0 ? (
|
||||||
<APIKeyList title="Your general keys" keys={generalKeys} reloadKeys={() => refreshState(true)} />
|
<APIKeyList title="Your API keys" keys={apiKeys} reloadKeys={() => refreshState(true)} />
|
||||||
) : (
|
) : (
|
||||||
<Alert $variant="info">No general API keys found.</Alert>
|
<Alert $variant="info">No API keys found.</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -105,6 +113,6 @@ const APIKeysPage = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
APIKeysPage.Layout = UserSettingsLayout;
|
DeveloperSettingsPage.Layout = UserSettingsLayout;
|
||||||
|
|
||||||
export default APIKeysPage;
|
export default DeveloperSettingsPage;
|
Reference in New Issue