mirror of https://github.com/BRAVO68WEB/shx.git
Merge pull request #109 from BRAVO68WEB/feature/api-integration
Feature/api integration
This commit is contained in:
commit
f65cac9912
|
@ -27,11 +27,11 @@ class ApiSdk {
|
|||
if (typeof window === 'undefined') {
|
||||
const { cookies } = await import('next/headers');
|
||||
config.headers['x-shx-api-key'] = cookies().get('apiKey')?.value;
|
||||
config.baseURL = cookies().get('instanceUrl')?.value;
|
||||
|
||||
} else {
|
||||
config.headers['x-shx-api-key'] = Cookies.get().apiKey ?? '';
|
||||
config.baseURL = Cookies.get('instanceUrl') ?? '';
|
||||
}
|
||||
config.baseURL = process.env.NEXT_PUBLIC_INSTANCE_URL
|
||||
|
||||
return config;
|
||||
});
|
||||
|
|
|
@ -6,24 +6,27 @@ export class Notes {
|
|||
constructor(axios: Axios) {
|
||||
this.axios = axios;
|
||||
}
|
||||
async getAllNotes(search?:string) {
|
||||
const res = await this.axios.get('/gist',{params:{search}});
|
||||
async getAllNotes(search?: string) {
|
||||
const res = await this.axios.get('/gist', { params: { search } });
|
||||
const data = res.data.data as INote[];
|
||||
return data;
|
||||
}
|
||||
async getSingleNote(id: string, passkey = '') {
|
||||
const res = await this.axios.get(`/gist/${id}`, {
|
||||
params: passkey ? { passkey } : {},
|
||||
});
|
||||
const data = res.data.data as INote;
|
||||
return data;
|
||||
}
|
||||
async uploadSingleNote(data: AddNoteType) {
|
||||
console.log("uploading")
|
||||
if(!data.passkey){
|
||||
delete data.passkey
|
||||
}
|
||||
console.log('uploading');
|
||||
if (!data.passkey) {
|
||||
delete data.passkey;
|
||||
}
|
||||
const res = await this.axios.post('/gist', data);
|
||||
return res.data.data as INote;
|
||||
}
|
||||
async deleteSingleNote({
|
||||
noteID,
|
||||
}: {
|
||||
noteID: string;
|
||||
}) {
|
||||
async deleteSingleNote({ noteID }: { noteID: string }) {
|
||||
const res = await this.axios.delete(`/gist/${noteID}`);
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,12 @@ export class Settings {
|
|||
const res = await this.axios.post('/settings', {key, value});
|
||||
return res
|
||||
}
|
||||
async getInstanceInfo(){
|
||||
const res = await this.axios.get('/info/sys');
|
||||
delete res.data.data.cpuUsage
|
||||
delete res.data.data.memoryUsage
|
||||
return res.data.data as ISysSettings
|
||||
}
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
"use client"
|
||||
|
||||
import React from 'react'
|
||||
|
||||
function ErrorPage() {
|
||||
return (
|
||||
<div>ErrorPage</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorPage
|
|
@ -1,23 +1,30 @@
|
|||
import { sidebarGroups } from '@/lib/sidebar';
|
||||
import { cookies } from 'next/headers';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Layout = ({ children }: DashboardLayoutProps) => {
|
||||
const cookieList = cookies();
|
||||
const apiKey = cookieList.has('apiKey');
|
||||
const masterKey = cookieList.has('masterKey');
|
||||
if (!apiKey || !masterKey) redirect('/');
|
||||
return (
|
||||
<div className="flex h-screen">
|
||||
<div className="sidebar w-full max-w-xs h-screen bg-black flex flex-col overflow-x-hidden overflow-y-auto">
|
||||
{sidebarGroups.map((sidebarGrp, index) => {
|
||||
return (
|
||||
<div key={index} className="menu-group p-4">
|
||||
<p className="title text-lg text-primary mb-2">{sidebarGrp.name}</p>
|
||||
<p className="title text-lg text-primary mb-2">
|
||||
{sidebarGrp.name}
|
||||
</p>
|
||||
{sidebarGrp.items.map((item, index) => {
|
||||
return (
|
||||
<Link
|
||||
key={index}
|
||||
key={index}
|
||||
href={item.href}
|
||||
className="p-2.5 block text-sm hover:bg-gray-900 w-full rounded-md"
|
||||
>
|
||||
|
@ -29,7 +36,7 @@ const Layout = ({ children }: DashboardLayoutProps) => {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
<main className='w-full h-full overflow-y-auto p-5'>{children}</main>
|
||||
<main className="w-full h-full overflow-y-auto p-5">{children}</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import Markdown from '@/components/Markdown';
|
||||
import axios from 'axios';
|
||||
import React from 'react';
|
||||
|
||||
const Dashboard = () => {
|
||||
const Dashboard = async () => {
|
||||
const { data } = await axios.get(
|
||||
'https://raw.githubusercontent.com/BRAVO68WEB/shx/dev/README.md'
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1 className="text-5xl">Dasbhboard</h1>
|
||||
<Markdown markdown={data} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,37 +1,94 @@
|
|||
'use client';
|
||||
|
||||
import Button from '@/components/ui/Button';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
import { Download } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
const configs = [
|
||||
{
|
||||
name: 'Lorem Ipsum Config',
|
||||
name: 'Image Config',
|
||||
filename: 'image.sxcu',
|
||||
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/image.sxcu?apikey=`,
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: 'Lorem Cnofig',
|
||||
id: '12',
|
||||
name: 'Gist Config',
|
||||
filename: 'gist.sxcu',
|
||||
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/gist.sxcu?apikey=`,
|
||||
id: '2',
|
||||
},
|
||||
{
|
||||
name: 'Lorem Config Config',
|
||||
id: '13',
|
||||
name: 'URL Config',
|
||||
filename: 'url.sxcu',
|
||||
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/url.sxcu?apikey=`,
|
||||
id: '3',
|
||||
},
|
||||
{
|
||||
name: 'Lorem ',
|
||||
id: '14',
|
||||
name: 'File Config',
|
||||
filename: 'file.sxcu',
|
||||
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/file.sxcu?apikey=`,
|
||||
id: '4',
|
||||
},
|
||||
];
|
||||
|
||||
const downloadFile = ({
|
||||
data,
|
||||
fileName,
|
||||
fileType,
|
||||
}: {
|
||||
data: string;
|
||||
fileName: string;
|
||||
fileType: string;
|
||||
}) => {
|
||||
// Create a blob with the data we want to download as a file
|
||||
const blob = new Blob([data], { type: fileType });
|
||||
// Create an anchor element and dispatch a click event on it
|
||||
// to trigger a download
|
||||
const a = document.createElement('a');
|
||||
a.download = fileName;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
const clickEvt = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
a.dispatchEvent(clickEvt);
|
||||
a.remove();
|
||||
};
|
||||
function Page() {
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
useEffect(() => {
|
||||
setApiKey(Cookies.get('apiKey') ?? '');
|
||||
},[]);
|
||||
|
||||
async function onDownloadFile(url: string, filename: string) {
|
||||
try {
|
||||
const { data } = await axios.get(url);
|
||||
console.log(data);
|
||||
downloadFile({
|
||||
data: JSON.stringify(data),
|
||||
fileName: filename,
|
||||
fileType: 'text/json',
|
||||
});
|
||||
} catch {
|
||||
toast.error('Error downloading file');
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{configs.map(({ name, id }) => {
|
||||
{configs.map(({ name, id, url, filename }) => {
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className="w-full my-3 p-4 flex items-center justify-between bg-gray-900 rounded"
|
||||
>
|
||||
<p>{name}</p>
|
||||
<Button size={'icon'} variant={'transparent'}>
|
||||
<Button
|
||||
size={'icon'}
|
||||
onClick={() => onDownloadFile(`${url}${apiKey}`, filename)}
|
||||
>
|
||||
<Download />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import api from '@/api';
|
||||
import React from 'react';
|
||||
|
||||
function Page() {
|
||||
const urls = [
|
||||
{
|
||||
id: 'afdas',
|
||||
originalURL: 'https://www.google.com',
|
||||
shortenedURL: 'https://www.google.com',
|
||||
},
|
||||
];
|
||||
async function Page() {
|
||||
const res = await api.settings.getInstanceInfo();
|
||||
const data = Object.entries(res);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -29,19 +25,13 @@ function Page() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y p-2">
|
||||
{urls.map(({ originalURL, shortenedURL, id }) => (
|
||||
{data.map((val, id) => (
|
||||
<tr className="bg-gray-900 rounded" key={id}>
|
||||
<td className="whitespace-nowrap py-5 pl-4 pr-20 text-sm font-medium text-white">
|
||||
<div className="flex items-center gap-3">
|
||||
{originalURL}
|
||||
|
||||
</div>
|
||||
<div className="flex items-center gap-3">{val[0]}</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
||||
<div className="flex items-center gap-3">
|
||||
{shortenedURL}
|
||||
|
||||
</div>
|
||||
<div className="flex items-center gap-3">{val[1]}</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import React, { ChangeEventHandler, useEffect, useState } from 'react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import TagInput from '@/components/TagInput';
|
||||
import api from '@/api';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
@ -112,21 +111,16 @@ function Page() {
|
|||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center w-full gap-4">
|
||||
<Input
|
||||
id="instance-url"
|
||||
withLabel
|
||||
label="Instance URL"
|
||||
type="text"
|
||||
className="w-full"
|
||||
/>
|
||||
<Button className="w-20 h-min ">Save</Button>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<TagInput
|
||||
tags={settings.imageExtensions}
|
||||
onAddTags={addImageExt}
|
||||
placeholder="Image Extensions"
|
||||
onChange={value =>
|
||||
setSettings(old => {
|
||||
return { ...old, imageExtensions: value };
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@ -143,6 +137,11 @@ function Page() {
|
|||
tags={settings.fileExtensions}
|
||||
onAddTags={addFileExt}
|
||||
placeholder="File Extensions"
|
||||
onChange={value =>
|
||||
setSettings(old => {
|
||||
return { ...old, fileExtensions: value };
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
onClick={() =>
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
|
||||
*{
|
||||
margin:0;
|
||||
padding:0;
|
||||
box-sizing: border-box;
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root{
|
||||
--primary:#3A86FF;
|
||||
--secondary:#8338EC;
|
||||
:root {
|
||||
--primary: #3a86ff;
|
||||
--secondary: #8338ec;
|
||||
}
|
||||
|
||||
body{
|
||||
color:white
|
||||
body {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.markdown-body p:nth-child(3) {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.markdown-body p:nth-child(4) {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.markdown-body p:nth-child(5) {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import './globals.css'
|
||||
import { Source_Code_Pro } from 'next/font/google'
|
||||
import React from 'react';
|
||||
import './globals.css';
|
||||
import { Source_Code_Pro } from 'next/font/google';
|
||||
import Providers from '@/components/Providers';
|
||||
|
||||
const inter = Source_Code_Pro({subsets:['latin']})
|
||||
const inter = Source_Code_Pro({ subsets: ['latin'] });
|
||||
|
||||
export const metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
}
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
|
||||
return (
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${inter.className} bg-black text-white min-h-screen`}>{children}</body>
|
||||
<body className={`${inter.className} bg-black text-white min-h-screen`}>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
'use client';
|
||||
|
||||
import api from '@/api';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import { Lock } from 'lucide-react';
|
||||
import React, { FormEventHandler, useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
interface NoteContentProps {
|
||||
data: INote | null;
|
||||
id: string;
|
||||
}
|
||||
|
||||
function NoteContent({ data, id }: NoteContentProps) {
|
||||
const [note, setNote] = useState(data);
|
||||
const [input, setInput] = useState('');
|
||||
const onSubmit: FormEventHandler = async evt => {
|
||||
evt.preventDefault();
|
||||
try {
|
||||
const data = await api.notes.getSingleNote(id, input);
|
||||
setNote(data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('Wrong passkey');
|
||||
setNote(null);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="w-1/2 p-10 border-primary border max-h-full overflow-y-auto">
|
||||
{note?.content ?? (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className="flex items-center justify-between gap-4 w-full"
|
||||
>
|
||||
<Input
|
||||
withLabel={true}
|
||||
placeholder="Enter passkey"
|
||||
label="Enter passkey"
|
||||
className="w-full"
|
||||
onChange={evt => setInput(evt.target.value)}
|
||||
value={input}
|
||||
/>
|
||||
<Button size={'icon'}>
|
||||
<Lock />
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoteContent;
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react'
|
||||
|
||||
function Loading() {
|
||||
return (
|
||||
<div>Loading</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Loading
|
|
@ -0,0 +1,25 @@
|
|||
import api from '@/api';
|
||||
import React from 'react';
|
||||
import NoteContent from './NoteContent';
|
||||
|
||||
interface NotePageProps {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
async function Page({ params: { id } }: NotePageProps) {
|
||||
let data: INote | null = null;
|
||||
try {
|
||||
data = await api.notes.getSingleNote(id);
|
||||
} catch {
|
||||
data = null;
|
||||
}
|
||||
return (
|
||||
<div className="w-full h-screen p-20 flex justify-center items-center">
|
||||
<NoteContent data={data} id={id} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
import api from '@/api';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { parseDate } from '@/lib/utils';
|
||||
import Cookies from 'js-cookie';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
|
@ -11,9 +14,16 @@ interface ApiKeyListProps {
|
|||
|
||||
function ApiKeyList({ data }: ApiKeyListProps) {
|
||||
const [apiKeys, setApiKeys] = useState(data);
|
||||
const onDisableApiKey = async (id: string) => {
|
||||
const router = useRouter();
|
||||
const onDisableApiKey = async (id: string, key: string) => {
|
||||
try {
|
||||
const apiKey = Cookies.get().apiKey as string;
|
||||
await api.apiKeys.disableApiKey(id);
|
||||
if (apiKey.endsWith(key.substring(9))) {
|
||||
Cookies.remove('apiKey');
|
||||
Cookies.remove('masterKey');
|
||||
router.replace('/');
|
||||
}
|
||||
setApiKeys(old => old.filter(key => key.keyID !== id));
|
||||
} catch (err) {
|
||||
console.error('Error Deleting api key');
|
||||
|
@ -37,10 +47,13 @@ function ApiKeyList({ data }: ApiKeyListProps) {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y p-2">
|
||||
{apiKeys.map(({ key, keyID }) => (
|
||||
{apiKeys.map(({ key, keyID, last_used }) => (
|
||||
<tr className="bg-gray-900 rounded" key={keyID}>
|
||||
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
||||
<p className="text-xl">{key}</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
Last Used: {parseDate(last_used)}
|
||||
</p>
|
||||
</td>
|
||||
<td className="relative whitespace-nowrap py-4 px-4 text-right text-sm font-medium icons flex center items-center gap-3">
|
||||
<Button
|
||||
|
@ -49,7 +62,7 @@ function ApiKeyList({ data }: ApiKeyListProps) {
|
|||
aria-label="Disable Api Key"
|
||||
title="Disable Api Key"
|
||||
className="rounded-full p-2 bg-red-100 text-red-600"
|
||||
onClick={() => onDisableApiKey(keyID)}
|
||||
onClick={() => onDisableApiKey(keyID, key)}
|
||||
>
|
||||
Disable
|
||||
</Button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Button from '@/components/ui/Button';
|
||||
import { Edit, Trash2 } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { ArrowUpRight, Copy, Trash2 } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface NotesLitsProps {
|
||||
data: INote[];
|
||||
|
@ -8,6 +8,11 @@ interface NotesLitsProps {
|
|||
}
|
||||
|
||||
function NotesList({ data, onDeleteNote }: NotesLitsProps) {
|
||||
function copyNoteLink(id: string) {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin.toString()}/notes/${id}`
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1">
|
||||
{data.map(note => {
|
||||
|
@ -30,12 +35,23 @@ function NotesList({ data, onDeleteNote }: NotesLitsProps) {
|
|||
<Button
|
||||
variant="transparent"
|
||||
size={'icon'}
|
||||
aria-label="Edit Note"
|
||||
title="Edit Note"
|
||||
aria-label="Copy Note"
|
||||
title="Copy Note"
|
||||
className="rounded-full hover:bg-black"
|
||||
onClick={() => copyNoteLink(note.gist_url_key)}
|
||||
>
|
||||
<Edit className="h-5 w-5 " />
|
||||
<Copy className="h-5 w-5 " />
|
||||
</Button>
|
||||
<Link
|
||||
href={`/notes/${note.gist_url_key}`}
|
||||
target="_blank"
|
||||
referrerPolicy="no-referrer"
|
||||
aria-label="Go to Note"
|
||||
title="Go to Note"
|
||||
className="rounded-full hover:bg-black p-3 h-min w-min"
|
||||
>
|
||||
<ArrowUpRight className="h-5 w-5 " />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { Trash, Edit2, ArrowUpRight, X } from 'lucide-react';
|
||||
import URLControls from './URLControls';
|
||||
|
@ -9,7 +9,6 @@ import Input from '@/components/ui/Input';
|
|||
import api from '@/api';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
interface ShortenUrlListProps {
|
||||
data: IUrl[];
|
||||
|
@ -27,7 +26,7 @@ function ShortenUrlList({ data }: ShortenUrlListProps) {
|
|||
state: false,
|
||||
});
|
||||
const [input, setInput] = useState('');
|
||||
const [instanceUrl, setInstanceURL] = useState('');
|
||||
|
||||
const onAddURL = async (url: string) => {
|
||||
try {
|
||||
await api.url.uploadUrl(url);
|
||||
|
@ -59,9 +58,6 @@ function ShortenUrlList({ data }: ShortenUrlListProps) {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setInstanceURL(Cookies.get('instanceUrl') ?? '');
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<URLControls onAddURL={onAddURL} />
|
||||
|
@ -103,10 +99,10 @@ function ShortenUrlList({ data }: ShortenUrlListProps) {
|
|||
</td>
|
||||
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
||||
<div className="flex items-center gap-3">
|
||||
<p className="w-80 truncate">{`${instanceUrl}/${short_key}`}</p>
|
||||
<p className="w-80 truncate">{`${process.env.NEXT_PUBLIC_INSTANCE_URL}/${short_key}`}</p>
|
||||
<a
|
||||
referrerPolicy="no-referrer"
|
||||
href={`${instanceUrl}/${short_key}`}
|
||||
href={`${process.env.NEXT_PUBLIC_INSTANCE_URL}/${short_key}`}
|
||||
target="_blank"
|
||||
className="p-2 bg-white bg-opacity-10 rounded cursor-pointer"
|
||||
>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Button from '@/components/ui/Button';
|
||||
import { ArrowUpRight, Trash } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, parseDate } from '@/lib/utils';
|
||||
import { UploadsListComponentProps } from '@/types/list';
|
||||
|
||||
export default function LinearList({
|
||||
|
@ -10,9 +10,6 @@ export default function LinearList({
|
|||
onDelete,
|
||||
}: UploadsListComponentProps) {
|
||||
// parse a date from gmt format to iso format
|
||||
const parseDate = (date: string) => {
|
||||
return new Date(date).toISOString().split('T')[0];
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-2 ">
|
||||
|
|
|
@ -18,9 +18,8 @@ const LoginForm = () => {
|
|||
resolver: zodResolver(LoginSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async ({ masterkey, instanceUrl }: LoginType) => {
|
||||
const onSubmit = async ({ masterkey }: LoginType) => {
|
||||
Cookies.set('masterKey', masterkey);
|
||||
Cookies.set('instanceUrl', instanceUrl);
|
||||
try {
|
||||
const res = await api.apiKeys.createKey();
|
||||
if (res) {
|
||||
|
@ -28,14 +27,12 @@ const LoginForm = () => {
|
|||
router.push('/dashboard');
|
||||
} else {
|
||||
Cookies.remove('masterKey');
|
||||
Cookies.remove('instanceUrl');
|
||||
Cookies.remove('apiKey');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('error');
|
||||
Cookies.remove('masterKey');
|
||||
Cookies.remove('instanceUrl');
|
||||
Cookies.remove('apiKey');
|
||||
}
|
||||
};
|
||||
|
@ -53,14 +50,6 @@ const LoginForm = () => {
|
|||
type="text"
|
||||
id="masterKey"
|
||||
/>
|
||||
<Input
|
||||
{...register('instanceUrl')}
|
||||
label="Instance Url"
|
||||
withLabel={true}
|
||||
placeholder="Instance Url"
|
||||
type="text"
|
||||
id="instanceUrl"
|
||||
/>
|
||||
<Button type="submit" className="mt-8">
|
||||
Sign in
|
||||
</Button>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
'use client';
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import showdown from 'showdown';
|
||||
import 'github-markdown-css';
|
||||
|
||||
const converter = new showdown.Converter();
|
||||
|
||||
interface MarkdownProps {
|
||||
markdown: string;
|
||||
}
|
||||
|
||||
function Markdown({ markdown }: MarkdownProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (ref.current?.innerHTML) ref.current.innerHTML = converter.makeHtml(markdown);
|
||||
}, [markdown]);
|
||||
return (
|
||||
<div className="markdown-body w-full p-10" ref={ref}>
|
||||
Markdown
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Markdown;
|
|
@ -0,0 +1,13 @@
|
|||
'use client';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
|
||||
function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Toaster position={'top-right'} reverseOrder={false} />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Providers;
|
|
@ -5,10 +5,11 @@ import Button from './ui/Button';
|
|||
interface TagInput {
|
||||
tags: string[];
|
||||
onAddTags: (value: string) => void;
|
||||
onChange: (value: string[]) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
function TagInput({ tags, placeholder, onAddTags }: TagInput) {
|
||||
function TagInput({ tags, placeholder, onAddTags, onChange }: TagInput) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const onSubmit: FormEventHandler<HTMLFormElement> = evt => {
|
||||
evt.preventDefault();
|
||||
|
@ -17,10 +18,17 @@ function TagInput({ tags, placeholder, onAddTags }: TagInput) {
|
|||
inputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
const onDelete = (i: number) => {
|
||||
onChange(tags.filter((_tag, index) => i !== index));
|
||||
};
|
||||
return (
|
||||
<div className="w-full flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<Button key={index} className="rounded w-min m-0 items-center">
|
||||
<Button
|
||||
onClick={() => onDelete(index)}
|
||||
key={index}
|
||||
className="rounded w-min m-0 items-center"
|
||||
>
|
||||
{tag}
|
||||
</Button>
|
||||
))}
|
||||
|
|
|
@ -32,12 +32,12 @@ export const sidebarGroups: SidebarGroup[] = [
|
|||
href: '/dashboard/utilities/instance-info',
|
||||
},
|
||||
{
|
||||
name: 'Uploads',
|
||||
name: 'Settings',
|
||||
href: '/dashboard/utilities/settings',
|
||||
},
|
||||
{
|
||||
name: 'Notes',
|
||||
href: '/dashboard/utilites/download-config',
|
||||
name: 'Download Config',
|
||||
href: '/dashboard/utilities/download-config',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -4,3 +4,7 @@ import { twMerge } from 'tailwind-merge';
|
|||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export const parseDate = (date: string) => {
|
||||
return new Date(date).toISOString().split('T')[0];
|
||||
};
|
|
@ -2,7 +2,6 @@ import { z } from 'zod';
|
|||
|
||||
export const LoginSchema = z.object({
|
||||
masterkey: z.string().max(256),
|
||||
instanceUrl: z.string().url(),
|
||||
});
|
||||
|
||||
export type LoginType = z.infer<typeof LoginSchema>;
|
||||
|
|
|
@ -23,15 +23,19 @@
|
|||
"clsx": "^1.2.1",
|
||||
"eslint": "8.41.0",
|
||||
"eslint-config-next": "13.4.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"install": "^0.13.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.221.0",
|
||||
"next": "^13.4.7",
|
||||
"next-auth": "^4.22.1",
|
||||
"npm": "^9.7.2",
|
||||
"postcss": "8.4.24",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.44.3",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"showdown": "^2.1.0",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "5.0.4",
|
||||
|
@ -46,6 +50,7 @@
|
|||
"@storybook/react": "^7.0.17",
|
||||
"@storybook/testing-library": "^0.0.14-next.2",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/showdown": "^2.0.1",
|
||||
"eslint-plugin-storybook": "^0.6.12",
|
||||
"storybook": "^7.0.17"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
interface IApiKey {
|
||||
key: string;
|
||||
keyID: string;
|
||||
last_used: string;
|
||||
}
|
||||
|
|
|
@ -3,4 +3,13 @@ interface ISettings {
|
|||
language: string;
|
||||
imageExtensions: string[];
|
||||
fileExtensions: string[];
|
||||
}
|
||||
}
|
||||
|
||||
interface ISysSettings {
|
||||
platform: string;
|
||||
arch: string;
|
||||
nodeVersion: string;
|
||||
uptime: string;
|
||||
kernelVersion: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue