Merge pull request #99 from BRAVO68WEB/feature/api-integration

Feature/api integration
This commit is contained in:
Jyotirmoy Bandyopadhayaya 2023-06-24 01:04:37 +05:30 committed by GitHub
commit 9525eb5202
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 993 additions and 426 deletions

View File

@ -0,0 +1,39 @@
import { Axios } from 'axios';
import Cookies from 'js-cookie';
export class ApiKeys {
axios: Axios;
constructor(axios: Axios) {
this.axios = axios;
}
private async getMasterKey() {
if (typeof window === 'undefined') {
const { cookies } = await import('next/headers');
return cookies().get('masterKey')?.value ?? '';
} else {
return Cookies.get('masterKey') ?? '';
}
}
async getAllKeys() {
const res = await this.axios.get(`/apiKey`, {
params: { masterkey: await this.getMasterKey() },
});
return res.data.data as IApiKey[];
}
async createKey() {
const res = await this.axios.post(
`/apiKey`,
{},
{ params: { masterkey: await this.getMasterKey() } }
);
return res.data.data.key as string;
}
async disableApiKey(id: string) {
const res = await this.axios.delete('/apiKey', {
params: { masterkey: await this.getMasterKey(), apikeyID: id },
});
return res;
}
}
export default ApiKeys;

View File

@ -1,33 +1,45 @@
import axios, { Axios } from 'axios';
import Uploads from './uploads';
import Notes from './notes';
import ApiKeys from './apiKeys';
import Url from './url';
import Settings from './settings';
import Cookies from 'js-cookie';
class ApiSdk {
private _axios: Axios;
private _apiKey: string;
private _instanceUrl: string;
uploads: Uploads;
notes: Notes;
apiKeys: ApiKeys;
url: Url;
settings: Settings;
constructor() {
this._instanceUrl = process.env.NEXT_APP_API_URL as string;
this._apiKey = '';
this._axios = axios.create()
this._axios = this.createAxios();
this.uploads = new Uploads(this._axios);
this.notes = new Notes(this._axios);
this.apiKeys = new ApiKeys(this._axios);
this.url = new Url(this._axios);
this.settings = new Settings(this._axios);
}
private _setAxios() {
this._axios.defaults.baseURL = this._instanceUrl
const headers:any = this._axios.defaults.headers
headers['x-shx-api-key'] = this._apiKey
private createAxios(): Axios {
const ax = axios.create();
ax.interceptors.request.use(async config => {
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') ?? '';
}
return config;
});
return ax;
}
getAxios() {
return this._axios;
}
setInstanceUrl(instanceUrl: string) {
this._instanceUrl = instanceUrl;
this._setAxios();
}
setApiKey(apiKey: string) {
this._apiKey = apiKey;
this._setAxios();
}
}
export default new ApiSdk();

View File

@ -0,0 +1,32 @@
import { AddNoteType } from '@/lib/validators/notes';
import { Axios } from 'axios';
export class Notes {
axios: Axios;
constructor(axios: Axios) {
this.axios = axios;
}
async getAllNotes(search?:string) {
const res = await this.axios.get('/gist',{params:{search}});
const data = res.data.data as INote[];
return data;
}
async uploadSingleNote(data: AddNoteType) {
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;
}) {
const res = await this.axios.delete(`/gist/${noteID}`);
return res;
}
}
export default Notes;

View File

@ -0,0 +1,19 @@
import { Axios } from 'axios';
export class Settings {
axios: Axios;
constructor(axios: Axios) {
this.axios = axios;
}
async getCurrentSettings() {
const res = await this.axios.get('/settings');
const data = res.data.data as ISettings;
return data;
}
async updateASetting(key: string, value:string|string[]){
const res = await this.axios.post('/settings', {key, value});
return res
}
}
export default Settings;

View File

@ -5,11 +5,21 @@ export class Uploads {
constructor(axios: Axios) {
this.axios = axios;
}
async getAllUploads() {
const res = await this.axios.get('/upload');
const data = res.data as IFile[];
async getAllUploads(search?: string){
const res = await this.axios.get('/upload',{params:{query:search}});
const data = res.data.data as IFile[];
return data;
}
async uploadSingleFile({file}:{file:File}){
const data = new FormData()
data.append('file', file)
const res = await this.axios.post('/upload/file', data);
return res.data.data as IFile
}
async deleteSingleFile({fileID,deleteToken}:{fileID:string,deleteToken:string}){
const res = await this.axios.get(`/upload/delete/${fileID}`,{params:{token:deleteToken}})
return res
}
}
export default Uploads;

View File

@ -0,0 +1,35 @@
import { Axios } from 'axios';
export class Url {
axios: Axios;
constructor(axios: Axios) {
this.axios = axios;
}
async getAllUrls() {
const res = await this.axios.get('/url');
const data = res.data.data as IUrl[];
return data;
}
async uploadUrl(url: string) {
const res = await this.axios.post('/url', { url });
return res.data.data as IUrl;
}
async editUrl({
original_url,
short_key,
id,
}: {
id: string;
original_url: string;
short_key: string;
}) {
const res = await this.axios.put(`/url/${id}`, { original_url, short_key });
return res
}
async deleteUrl(id: string) {
const res = await this.axios.delete(`/url/${id}`);
return res;
}
}
export default Url;

View File

@ -0,0 +1,9 @@
"use client"
import React from 'react';
function ErrorPage() {
return <div>ErrorPage</div>;
}
export default ErrorPage;

View File

@ -1,21 +1,23 @@
import ApiKeyList from '@/components/Lists/ApiKeyList'
import Button from '@/components/ui/Button'
import { Plus } from 'lucide-react'
import React from 'react'
import api from '@/api';
import ApiKeyList from '@/components/Lists/ApiKeyList';
import Button from '@/components/ui/Button';
import { Plus } from 'lucide-react';
import React from 'react';
function Page() {
return (
<div className=''>
<h1 className='text-4xl'>
Your Api Keys
</h1>
<div className="flex flex-col gap-4">
<Button className='w-min text-lg px-4 flex items-center gap-2'>Add <Plus /></Button>
<ApiKeyList />
</div>
async function Page() {
const res = await api.apiKeys.getAllKeys();
</div>
)
return (
<div className="">
<h1 className="text-4xl">Your Api Keys</h1>
<div className="flex flex-col gap-4">
<Button className="w-min text-lg px-4 flex items-center gap-2">
Add <Plus />
</Button>
<ApiKeyList data={res} />
</div>
</div>
);
}
export default Page
export default Page;

View File

@ -0,0 +1,9 @@
"use client"
import React from 'react';
function ErrorPage() {
return <div>ErrorPage</div>;
}
export default ErrorPage;

View File

@ -1,14 +1,15 @@
import api from '@/api';
import Notes from '@/components/Notes';
import React from 'react'
import React from 'react';
function Page() {
return (
async function Page() {
const data = await api.notes.getAllNotes();
return (
<>
<h1 className="text-4xl">Notes</h1>
<Notes />
<Notes data={data} />
</>
);
}
export default Page
export default Page;

View File

@ -0,0 +1,7 @@
'use client';
function ErrorPage() {
return <div>ErrorPage</div>;
}
export default ErrorPage;

View File

@ -1,8 +1,10 @@
import api from '@/api';
import UploadsList from '@/components/Lists/UploadsList';
import React from 'react';
const Uploads = () => {
return <UploadsList />;
const Uploads = async () => {
const uploads = await api.uploads.getAllUploads();
return <UploadsList data={uploads}/>;
};
export default Uploads;

View File

@ -1,11 +1,14 @@
import api from '@/api'
import ShortenUrlList from '@/components/Lists/ShortenUrlList'
import React from 'react'
function Page() {
async function Page() {
const res = await api.url.getAllUrls()
return (
<>
<h1 className="text-4xl">URL Shortener</h1>
<ShortenUrlList />
<ShortenUrlList data={res} />
</>
)
}

View File

@ -0,0 +1,11 @@
"use client"
import React from 'react'
function ErrorPage() {
return (
<div>ErrorPage</div>
)
}
export default ErrorPage

View File

@ -1,61 +1,118 @@
"use client"
'use client';
import React, { useState } from 'react';
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';
function Page() {
const [imageExts, setImageExts] = useState<string[]>([]);
const [fileExts, setFileExts] = useState<string[]>([]);
const addImageExt = (tag: string) => {
setImageExts(old => {
return [...old, tag];
});
};
const addFileExt = (tag: string) => {
setFileExts(old => {
return [...old, tag];
});
};
const [settings, setSettings] = useState<ISettings>({
imageExtensions: [],
fileExtensions: [],
theme: '',
language: '',
});
const onChangeInput: ChangeEventHandler<
HTMLInputElement | HTMLSelectElement
> = evt => {
const { name, value } = evt.target;
setSettings(old => ({
...old,
[name]: value,
}));
};
const addImageExt = (tag: string) => {
setSettings(old => {
return {
...old,
imageExtensions: [...old.imageExtensions, tag],
};
});
};
const addFileExt = (tag: string) => {
setSettings(old => {
return {
...old,
fileExtensions: [...old.fileExtensions, tag],
};
});
};
const getSettings = async () => {
const res = await api.settings.getCurrentSettings();
setSettings(res);
};
const updateSettings = async (key: string, value: string | string[]) => {
try {
await api.settings.updateASetting(key, value);
} catch (err) {
console.error(err);
toast.error('error updating setting');
}
};
useEffect(() => {
getSettings();
}, []);
return (
<div className="flex flex-col gap-8 w-full mt-10">
<div>
<label
htmlFor="theme"
className="block text-sm font-medium leading-6 text-white"
<div className="flex items-center gap-4 w-full">
<div className="w-full">
<label
htmlFor="theme"
className="block text-sm font-medium leading-6 text-white"
>
Theme
</label>
<select
id="theme"
name="theme"
className="mt-2 block w-full bg-transparent rounded-md py-1.5 pl-3 pr-10 text-white sm:text-sm sm:leading-6 border-primary border"
value={settings.theme}
onChange={onChangeInput}
>
<option value={'dark'}>Dark</option>
</select>
</div>
<Button
onClick={() => updateSettings('theme', settings.theme)}
className="w-20 h-min mt-auto"
>
Theme
</label>
<select
id="theme"
name="theme"
className="mt-2 block w-full bg-transparent rounded-md py-1.5 pl-3 pr-10 text-white sm:text-sm sm:leading-6 border-primary border"
defaultValue="dark"
>
<option value={'dark'}>Dark</option>
</select>
Save
</Button>
</div>
<div>
<label
htmlFor="Language"
className="block text-sm font-medium leading-6 text-white"
<div className="flex w-full items-center gap-4">
<div className="w-full">
<label
htmlFor="Language"
className="block text-sm font-medium leading-6 text-white"
>
Language
</label>
<select
id="Language"
name="language"
className="mt-2 bg-transparent block w-full rounded-md py-1.5 pl-3 pr-10 text-white sm:text-sm sm:leading-6 border border-primary"
value={settings.language}
onChange={onChangeInput}
>
<option value={'en'}>English</option>
</select>
</div>
<Button
onClick={() => updateSettings('language', settings.language)}
className="w-20 h-min mt-auto"
>
Language
</label>
<select
id="Language"
name="Language"
className="mt-2 bg-transparent block w-full rounded-md py-1.5 pl-3 pr-10 text-white sm:text-sm sm:leading-6 border border-primary"
defaultValue="en"
>
<option value={'en'}>English</option>
</select>
Save
</Button>
</div>
<div className="flex items-center w-full">
<div className="flex items-center w-full gap-4">
<Input
id="instance-url"
withLabel
@ -63,18 +120,39 @@ function Page() {
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"
/>
<Button
onClick={() =>
updateSettings('imageExtensions', settings.imageExtensions)
}
className="w-20 h-min "
>
Save
</Button>
</div>
<div className="flex gap-2">
<TagInput
tags={settings.fileExtensions}
onAddTags={addFileExt}
placeholder="File Extensions"
/>
<Button
onClick={() =>
updateSettings('fileExtensions', settings.fileExtensions)
}
className="w-20 h-min "
>
Save
</Button>
</div>
<TagInput
tags={imageExts}
onAddTags={addImageExt}
placeholder="Image Extensions"
/>
<TagInput
tags={fileExts}
onAddTags={addFileExt}
placeholder="File Extensions"
/>
<Button>Save</Button>
</div>
);
}

View File

@ -1,4 +1,3 @@
import api from '@/api'
import './globals.css'
import { Source_Code_Pro } from 'next/font/google'
@ -14,9 +13,7 @@ export default function RootLayout({
}: {
children: React.ReactNode
}) {
api.setInstanceUrl("http://localhost:4000")
api.setApiKey('SHX-uyblf-ixuiz');
console.log(api.getAxios().defaults)
return (
<html lang="en">
<body className={`${inter.className} bg-black text-white min-h-screen`}>{children}</body>

View File

@ -1,25 +1,11 @@
import Button from "@/components/ui/Button";
import {Github} from "lucide-react"
import LoginForm from '@/components/LoginForm';
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center text-center p-24 ">
<div className="content max-w-3xl flex flex-col gap-2">
<h1 className="text-5xl text-primary">SHX</h1>
<p className="text-lg ">
An platform platform meant to store and share files, images, text and
URLs with ease
</p>
<p className="text-2xl my-8">Coming Soon...</p>
<p className="text-lg">Find us on:</p>
<div className="flex items-center justify-evenly">
<a href="https://github.com/BRAVO68WEB/shx" target="_blank" >
<Button size="icon" className="icon">
<Github className="h-8 w-8" />
</Button>
</a>
</div>
</div>
return (
<main className="w-screen h-screen flex items-center justify-center">
<LoginForm />
</main>
);
}

View File

@ -0,0 +1,25 @@
import Button from '@/components/ui/Button';
import { Github } from 'lucide-react';
export default function ComingSoon() {
return (
<main className="flex min-h-screen flex-col items-center justify-center text-center p-24 ">
<div className="content max-w-3xl flex flex-col gap-2">
<h1 className="text-5xl text-primary">SHX</h1>
<p className="text-lg ">
An platform platform meant to store and share files, images, text and
URLs with ease
</p>
<p className="text-2xl my-8">Coming Soon...</p>
<p className="text-lg">Find us on:</p>
<div className="flex items-center justify-evenly">
<a href="https://github.com/BRAVO68WEB/shx" target="_blank">
<Button size="icon" className="icon">
<Github className="h-8 w-8" />
</Button>
</a>
</div>
</div>
</main>
);
}

View File

@ -1,20 +1,25 @@
'use client';
import api from '@/api';
import Button from '@/components/ui/Button';
import React from 'react';
import React, { useState } from 'react';
import { toast } from 'react-hot-toast';
const apiKeys = [
{
key: 'asdfasd****fsadf',
enabled: true,
lastUsed: 'asdfasdfasdf',
},
{
key: 'disabled****fsadf',
enabled: false,
lastUsed: 'asdfasdfasdf',
},
];
interface ApiKeyListProps {
data: IApiKey[];
}
function ApiKeyList() {
function ApiKeyList({ data }: ApiKeyListProps) {
const [apiKeys, setApiKeys] = useState(data);
const onDisableApiKey = async (id: string) => {
try {
await api.apiKeys.disableApiKey(id);
setApiKeys(old => old.filter(key => key.keyID !== id));
} catch (err) {
console.error('Error Deleting api key');
toast.error('Error deleting api key');
}
};
return (
<div>
<table className="min-w-full divide-y divide-gray-700">
@ -32,34 +37,22 @@ function ApiKeyList() {
</tr>
</thead>
<tbody className="divide-y p-2">
{apiKeys.map(({ key, enabled,lastUsed }, index) => (
<tr className="bg-gray-900 rounded" key={index}>
{apiKeys.map(({ key, keyID }) => (
<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: {lastUsed}</p>
<p className="text-xl">{key}</p>
</td>
<td className="relative whitespace-nowrap py-4 px-4 text-right text-sm font-medium icons flex center items-center gap-3">
{enabled ? (
<Button
variant="transparent"
size={'icon'}
aria-label="Disable Api Key"
title="Disable Api Key"
className="rounded-full p-2 bg-red-100 text-red-600"
>
Disable
</Button>
) : (
<Button
variant="transparent"
size={'icon'}
aria-label="Enable Api Key"
title="Enable Api Key"
className="rounded-full p-2 text-green-600 bg-green-100"
>
Enable
</Button>
)}
<Button
variant="transparent"
size={'icon'}
aria-label="Disable Api Key"
title="Disable Api Key"
className="rounded-full p-2 bg-red-100 text-red-600"
onClick={() => onDisableApiKey(keyID)}
>
Disable
</Button>
</td>
</tr>
))}

View File

@ -2,39 +2,44 @@ import Button from '@/components/ui/Button';
import { Edit, Trash2 } from 'lucide-react';
import React from 'react';
function NotesList() {
interface NotesLitsProps {
data: INote[];
onDeleteNote: (note: string) => void;
}
function NotesList({ data, onDeleteNote }: NotesLitsProps) {
return (
<div className="flex flex-col w-full gap-1">
<div className="bg-gray-900 p-5 flex w-full gap-2">
<div className="text flex-1">
<p className="w-full max-w-lg">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.
</p>
</div>
<div className="buttons flex h-min gap-3">
<Button
variant="transparent"
size={'icon'}
aria-label="Delete Image"
title="Delete Image"
className="rounded-full hover:bg-red-50 hover:text-red-600 text-red-300"
>
<Trash2 className="h-5 w-5" />
</Button>
<Button
variant="transparent"
size={'icon'}
aria-label="Edit Note"
title="Edit Note"
className="rounded-full hover:bg-black"
>
<Edit className="h-5 w-5 " />
</Button>
</div>
</div>
{data.map(note => {
return (
<div key={note.gistID} className="bg-gray-900 p-5 flex w-full gap-2">
<div className="text flex-1">
<p className="w-full max-w-lg">{note.content}</p>
</div>
<div className="buttons flex h-min gap-3">
<Button
variant="transparent"
size={'icon'}
aria-label="Delete Note"
title="Delete Note"
className="rounded-full hover:bg-red-50 hover:text-red-600 text-red-300"
onClick={() => onDeleteNote(note.gistID)}
>
<Trash2 className="h-5 w-5" />
</Button>
<Button
variant="transparent"
size={'icon'}
aria-label="Edit Note"
title="Edit Note"
className="rounded-full hover:bg-black"
>
<Edit className="h-5 w-5 " />
</Button>
</div>
</div>
);
})}
</div>
);
}

View File

@ -1,4 +1,4 @@
"use client"
'use client';
import Modal from '@/components/Modal';
import Button from '@/components/ui/Button';
@ -6,10 +6,19 @@ import Input from '@/components/ui/Input';
import { Plus, X } from 'lucide-react';
import React, { useState } from 'react';
function ULRControls() {
const [addModalOpen, setAddModalOpen] = useState(false);
interface URLContrlProps {
onAddURL: (url: string) => Promise<void>;
}
function URLControls({ onAddURL }: URLContrlProps) {
const [addModalOpen, setAddModalOpen] = useState(false);
const [input, setInput] = useState('');
const onClick = async () => {
if (input.trim() === '') return;
await onAddURL(input);
setAddModalOpen(false);
};
return (
<>
<div className="flex gap-6 items-cetner w-full">
@ -41,18 +50,18 @@ function ULRControls() {
</div>
<Input
type="text"
id='url'
name="url"
withLabel={true}
label='Original URL'
id="url"
name="url"
withLabel={true}
label="Original URL"
value={input}
onChange={evt => setInput(evt.target.value)}
/>
<Button onClick={() => setAddModalOpen(false)} >
Add URl
</Button>
<Button onClick={onClick}>Add URl</Button>
</div>
</Modal>
</>
);
}
export default ULRControls;
export default URLControls;

View File

@ -1,31 +1,76 @@
"use client"
'use client';
import React,{useState} from 'react';
import React, { useEffect, useState } from 'react';
import Button from '@/components/ui/Button';
import { Trash, Edit2, ArrowUpRight,X } from 'lucide-react';
import ULRControls from './URLControls';
import { Trash, Edit2, ArrowUpRight, X } from 'lucide-react';
import URLControls from './URLControls';
import Modal from '@/components/Modal';
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';
const urls = [
{
id: 'afdas',
originalURL: 'https://www.google.com',
shortenedURL: 'https://www.google.com',
},
];
interface ShortenUrlListProps {
data: IUrl[];
}
function ShortenUrlList() {
const [editURLModal,setEditURLModal] = useState(false)
interface EditUrlModal {
state: boolean;
id?: string;
url?: string;
}
function ShortenUrlList({ data }: ShortenUrlListProps) {
const router = useRouter();
const [editURLModal, setEditURLModal] = useState<EditUrlModal>({
state: false,
});
const [input, setInput] = useState('');
const [instanceUrl, setInstanceURL] = useState('');
const onAddURL = async (url: string) => {
try {
await api.url.uploadUrl(url);
router.refresh();
} catch (e) {
console.error(e);
toast.error('Error adding url');
}
};
const onEditUrl = async (url: string, id: string) => {
if (!input.trim()) return;
try {
await api.url.editUrl({ original_url: url, id: id, short_key: input });
router.refresh();
} catch (e) {
console.error(e);
toast.error('Error updating url');
} finally {
setEditURLModal({ state: false });
}
};
const onDeleteUrl = async (id: string) => {
try {
await api.url.deleteUrl(id);
router.refresh();
} catch (e) {
console.error(e);
toast.error('Error deleting url');
}
};
useEffect(() => {
setInstanceURL(Cookies.get('instanceUrl') ?? '');
}, []);
return (
<>
<ULRControls />
<URLControls onAddURL={onAddURL} />
<table className="min-w-full divide-y divide-gray-700">
<thead className="p-2">
<tr>
<th
scope="col"
className="py-3.5 pl-4 pr-5 text-left text-lg font-semibold text-white"
className="py-3.5 pl-4 pr-5 text-left text-lg font-semibold text-white"
>
Original URL
</th>
@ -41,14 +86,14 @@ function ShortenUrlList() {
</tr>
</thead>
<tbody className="divide-y p-2">
{urls.map(({ originalURL, shortenedURL, id }) => (
<tr className="bg-gray-900 rounded" key={id}>
<td className="whitespace-nowrap pl-4 pr-20 text-sm font-medium text-white">
{data.map(({ original_url, urlID, short_key }) => (
<tr className="bg-gray-900 rounded" key={urlID}>
<td className="whitespace-nowrap pl-4 pr-20 truncate text-sm font-medium text-white">
<div className="flex items-center gap-3">
{originalURL}
<p className="w-80 truncate">{original_url}</p>
<a
referrerPolicy="no-referrer"
href={originalURL}
href={original_url}
target="_blank"
className="p-2 bg-white bg-opacity-10 rounded cursor-pointer"
>
@ -58,10 +103,10 @@ function ShortenUrlList() {
</td>
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
<div className="flex items-center gap-3">
{shortenedURL}
<p className="w-80 truncate">{`${instanceUrl}/${short_key}`}</p>
<a
referrerPolicy="no-referrer"
href={shortenedURL}
href={`${instanceUrl}/${short_key}`}
target="_blank"
className="p-2 bg-white bg-opacity-10 rounded cursor-pointer"
>
@ -76,6 +121,7 @@ function ShortenUrlList() {
aria-label="Delete URL"
title="Delete URL"
className="rounded-full p-2 bg-red-100 text-red-600"
onClick={() => onDeleteUrl(urlID)}
>
<Trash className="h-4 w-4 " />
</Button>
@ -85,7 +131,13 @@ function ShortenUrlList() {
aria-label="Edit URl Slug"
title="Edit URL Slug"
className="rounded-full p-2 hover:bg-black"
onClick={() => setEditURLModal(true)}
onClick={() =>
setEditURLModal({
state: true,
id: urlID,
url: original_url,
})
}
>
<Edit2 className="h-4 w-4 " />
</Button>
@ -94,11 +146,14 @@ function ShortenUrlList() {
))}
</tbody>
</table>
<Modal open={editURLModal} onClose={() => setEditURLModal(false)}>
<Modal
open={editURLModal.state}
onClose={() => setEditURLModal({ state: false })}
>
<div className="p-2">
<div className="controls w-full flex items-center justify-end mb-4">
<Button
onClick={() => setEditURLModal(false)}
onClick={() => setEditURLModal({ state: false })}
size={'icon'}
aria-label="Reset"
title="Reset"
@ -112,8 +167,16 @@ function ShortenUrlList() {
name="new_slug"
withLabel={true}
label="New Slug"
value={input}
onChange={evt => setInput(evt.target.value)}
/>
<Button onClick={() => setEditURLModal(false)}>Modify URl</Button>
<Button
onClick={() =>
onEditUrl(editURLModal.url ?? '', editURLModal.id ?? '')
}
>
Modify URl
</Button>
</div>
</Modal>
</>

View File

@ -1,30 +1,23 @@
import Button from '@/components/ui/Button';
import { UploadsListComponentProps } from '@/types/list';
import { ArrowUpRight, Trash } from 'lucide-react';
import React from 'react';
const files: UploadsListFile[] = [
{
name: 'fasdfasd fasd fas dfasa.png',
date: '2023-05-29T08:08:07.289624+00:00',
src: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072821_1280.jpg',
},
];
export default function GridList() {
const GridList: React.FC<UploadsListComponentProps> = ({ data,onDelete }) => {
return (
<div className="w-full grid grid-cols-3 gap-2 p-4">
{files.map((file, index) => (
{data.map(file => (
<div
key={index}
key={file.fileID}
className="grid-item overflow-hidden rounded-md w-full aspect-square flex items-center justify-center bg-gray-900 relative group"
>
<img
src={file.src}
src={file.upload_url}
alt=""
className="w-full h-full object-contain object-center"
/>
<div className="flex items-center justify-between gap-5 w-full absolute bottom-0 left-0 right opacity-0 translate-y-full p-4 group-hover:opacity-100 group-hover:translate-y-0 transition-all">
<p className='w-full truncate'>{file.name}</p>
<p className="w-full truncate">{file.filename}</p>
<div className="controls flex items-center gap-5">
<Button
variant="transparent"
@ -32,22 +25,24 @@ export default function GridList() {
aria-label="Delete Image"
title="Delete Image"
className="rounded-full p-2 hover:bg-red-50 hover:text-red-600 text-red-300"
onClick={() => onDelete(file.fileID,file.deleteToken)}
>
<Trash className="h-4 w-4 " />
</Button>
<Button
variant="transparent"
size={'icon'}
aria-label="Open Image"
title="Open Image"
<a
href={file.upload_url}
target="_blank"
referrerPolicy="no-referrer"
className="rounded-full p-2 hover:bg-black"
>
<ArrowUpRight className="h-4 w-4 " />
</Button>
</a>
</div>
</div>
</div>
))}
</div>
);
}
};
export default GridList;

View File

@ -2,35 +2,18 @@ import Button from '@/components/ui/Button';
import { ArrowUpRight, Trash } from 'lucide-react';
import React from 'react';
import { cn } from '@/lib/utils';
import { UploadsListComponentProps } from '@/types/list';
const files: UploadsListFile[] = [
{
name: 'Lindsay Walton.png',
date: '2023-05-29T08:08:07.289624+00:00',
src: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072821_1280.jpg',
},
{
name: 'Wlaton Lindsay.jpg',
date: '2023-05-30T08:08:07.289624+00:00',
src: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072821_1280.jpg',
},
];
interface LinearListProps{
edit: boolean
}
export default function LinearList({edit}:LinearListProps) {
export default function LinearList({
edit,
data,
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 ">
<table className="min-w-full divide-y divide-gray-700">
@ -57,45 +40,48 @@ export default function LinearList({edit}:LinearListProps) {
</tr>
</thead>
<tbody className="divide-y p-2">
{files.map((person, index) => (
<tr className="bg-gray-900 rounded" key={index}>
<td className="relative whitespace-nowrap p-4 text-right text-sm font-medium">
<input
type="checkbox"
className={cn(
'h-4 w-4 rounded bg-transparent border-primary text-primary',
{ hidden: !edit }
)}
/>
</td>
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
{person.name}
</td>
<td className="whitespace-nowrap px-9 py-4 text-sm text-gray-300">
{parseDate(person.date)}
</td>
<td className="relative whitespace-nowrap py-4 px-4 text-right text-sm font-medium icons flex center items-center gap-3">
<Button
variant="transparent"
size={'icon'}
aria-label="Delete Image"
title="Delete Image"
className="rounded-full p-2 hover:bg-red-50 hover:text-red-600 text-red-300"
>
<Trash className="h-4 w-4 " />
</Button>
<Button
variant="transparent"
size={'icon'}
aria-label="Open Image"
title="Open Image"
className="rounded-full p-2 hover:bg-black"
>
<ArrowUpRight className="h-4 w-4 " />
</Button>
</td>
</tr>
))}
{data.map(
({ fileID, filename, uploaded_at, upload_url, deleteToken }) => (
<tr className="bg-gray-900 rounded" key={fileID}>
<td className="relative whitespace-nowrap p-4 text-right text-sm font-medium">
<input
type="checkbox"
className={cn(
'h-4 w-4 rounded bg-transparent border-primary text-primary',
{ hidden: !edit }
)}
/>
</td>
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
{filename}
</td>
<td className="whitespace-nowrap px-9 py-4 text-sm text-gray-300">
{parseDate(uploaded_at)}
</td>
<td className="relative whitespace-nowrap py-4 px-4 text-right text-sm font-medium icons flex center items-center gap-3">
<Button
variant="transparent"
size={'icon'}
aria-label="Delete Image"
title="Delete Image"
className="rounded-full p-2 hover:bg-red-50 hover:text-red-600 text-red-300"
onClick={() => onDelete(fileID, deleteToken)}
>
<Trash className="h-4 w-4 " />
</Button>
<a
href={upload_url}
target="_blank"
download={false}
referrerPolicy="no-referrer"
className="rounded-full p-2 hover:bg-black"
>
<ArrowUpRight className="h-4 w-4 " />
</a>
</td>
</tr>
)
)}
</tbody>
</table>
</div>

View File

@ -1,30 +1,53 @@
import api from '@/api';
import Modal from '@/components/Modal';
import Button from '@/components/ui/Button';
import Input from "@/components/ui/Input"
import Input from '@/components/ui/Input';
import { Edit, Plus, X } from 'lucide-react';
import React, { useState } from 'react'
import React, { useState } from 'react';
import { toast } from 'react-hot-toast';
interface UploadControlsProps{
onEditClick: () => void;
interface UploadControlsProps {
onEditClick: () => void;
onAddFile: (file: IFile) => void;
onSearchInputChange: (input: string) => void;
}
function UploadsControls({onEditClick}:UploadControlsProps) {
function UploadsControls({
onEditClick,
onAddFile,
onSearchInputChange,
}: UploadControlsProps) {
const [uploadModalOpen, setUploadModalOpen] = useState(false);
const [fileUpload, setFileUpload] = useState<File | null>(null);
const fileInputChange: React.ChangeEventHandler<HTMLInputElement> = evt => {
const fileInputChange: React.ChangeEventHandler<
HTMLInputElement
> = async evt => {
if (evt.target.value) {
setFileUpload(evt.target.files ? evt.target.files[0] : null);
try {
const file = evt.target.files ? evt.target.files[0] : null;
setFileUpload(file);
if (!file) return;
const res = await api.uploads.uploadSingleFile({ file });
onAddFile(res);
} catch {
toast.error('Error Uploading File');
} finally {
setFileUpload(null);
setUploadModalOpen(false);
}
}
};
return (
return (
<>
<div className="flex gap-6 items-cetner w-full">
<Input id="search" name="search" placeholder="Search" />
<Input
id="search"
name="search"
placeholder="Search"
onChange={evt => onSearchInputChange(evt.target.value)}
/>
<div className="flex items-center">22/33</div>
@ -37,7 +60,9 @@ function UploadsControls({onEditClick}:UploadControlsProps) {
<span>Add</span> <Plus />
</Button>
<Button
onClick={() => {onEditClick()}}
onClick={() => {
onEditClick();
}}
className="my-2 flex justify-between items-center w-auto gap-2"
>
<span>Edit</span> <Edit />
@ -59,7 +84,7 @@ function UploadsControls({onEditClick}:UploadControlsProps) {
<label
htmlFor="fileUpload"
className="input w-full h-20 border-primary border flex justify-center items-center"
className="input w-full h-40 border-primary border flex justify-center items-center"
>
{fileUpload ? (
<img
@ -84,4 +109,4 @@ function UploadsControls({onEditClick}:UploadControlsProps) {
);
}
export default UploadsControls
export default UploadsControls;

View File

@ -6,34 +6,68 @@ import Button from '@/components/ui/Button';
import LinearList from './LinearList';
import GridList from './GridList';
import UploadsControls from './UploadsControls';
import { UploadsListComponentProps } from '@/types/list';
import { toast } from 'react-hot-toast';
import api from '@/api';
import { useDebounce } from '@/hooks/useDebounce';
type ListOption = 0 | 1;
type ListOptions = {
name: string;
Icon: LucideIcon;
List: React.FC;
List: React.FC<UploadsListComponentProps>;
}[];
const listOptions: ListOptions = [
{ name: 'list', Icon: ListIcon, List: LinearList as React.FC<any> },
{ name: 'list', Icon: ListIcon, List: LinearList },
{ name: 'grid', Icon: GridIcon, List: GridList },
];
const UploadsList = () => {
interface UploadsListProps {
data: IFile[];
}
const UploadsList: React.FC<UploadsListProps> = ({ data }) => {
const [listOption, setListOption] = useState<ListOption>(0);
const [files, setFiles] = useState<IFile[]>(data);
const [edit, setEdit] = useState<boolean>(false);
const searchFiles = useDebounce(async (search: string) => {
const res = await api.uploads.getAllUploads(search);
setFiles(res);
}, 500);
const toggleEdit = () => setEdit(!edit);
const ListComponent:React.FC<any> = listOptions[listOption].List
function changeListOption(option: ListOption){
return () => setListOption(option)
const ListComponent = listOptions[listOption].List;
function changeListOption(option: ListOption) {
return () => setListOption(option);
}
const onAddFile = (file: IFile) => {
setFiles([file, ...files]);
};
const onDelete = async (fileID: string, deleteToken: string) => {
try {
await api.uploads.deleteSingleFile({ fileID, deleteToken });
setFiles(old => {
return old.filter(file => file.fileID !== fileID);
});
} catch (e) {
console.error(e);
toast.error('Error deleting file');
}
};
const onSearchChange = (input: string) => {
searchFiles(input);
};
return (
<div className='p-5'>
<div className="p-5">
<div className="toolbar flex py-2 bg-primary items-center justify-center gap-5 mb-10 rounded-md">
{listOptions.map(({ name, Icon }, index) => {
return (
@ -50,8 +84,12 @@ const UploadsList = () => {
);
})}
</div>
<UploadsControls onEditClick={toggleEdit} />
<ListComponent edit={edit}/>
<UploadsControls
onEditClick={toggleEdit}
onAddFile={onAddFile}
onSearchInputChange={onSearchChange}
/>
<ListComponent edit={edit} data={files} onDelete={onDelete} />
</div>
);
};

View File

@ -1,28 +1,66 @@
'use client';
import React, { FormEventHandler } from 'react';
import React from 'react';
import Button from './ui/Button';
import Input from './ui/Input';
import Cookies from 'js-cookie';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { LoginSchema, LoginType } from '@/lib/validators/login';
import api from '@/api';
import { toast } from 'react-hot-toast';
import { useRouter } from 'next/navigation';
const LoginForm = () => {
const router = useRouter();
const onSubmit:FormEventHandler<HTMLFormElement> = (evt) => {
evt.preventDefault();
}
const { register, handleSubmit } = useForm<LoginType>({
resolver: zodResolver(LoginSchema),
});
const onSubmit = async ({ masterkey, instanceUrl }: LoginType) => {
Cookies.set('masterKey', masterkey);
Cookies.set('instanceUrl', instanceUrl);
try {
const res = await api.apiKeys.createKey();
if (res) {
Cookies.set('apiKey', res);
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');
}
};
return (
<form
onSubmit={onSubmit}
onSubmit={handleSubmit(onSubmit)}
className="w-full max-w-2xl text-primary border border-primary rounded-lg p-10"
>
<Input
{...register('masterkey')}
label="Master Key"
withLabel={true}
placeholder="Master Key"
type="text"
name="masterKey"
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>

View File

@ -1,64 +1,95 @@
'use client';
import React, { useRef, useState } from 'react';
import React, { useState } from 'react';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import { PlusIcon, SearchIcon } from 'lucide-react';
import { PlusIcon } from 'lucide-react';
import NotesList from './Lists/NotesList';
import TextArea from './ui/TextArea';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { AddNoteType, addNoteSchema } from '@/lib/validators/notes';
import { cn } from '@/lib/utils';
import api from '@/api';
import { toast } from 'react-hot-toast';
import { useDebounce } from '@/hooks/useDebounce';
import Modal from './Modal';
function Notes() {
const dialogRef = useRef<HTMLDialogElement>(null);
const [dialogOpen,setDialogOpen] = useState(false)
const {
register,
handleSubmit,
} = useForm<AddNoteType>({
interface NotesProps {
data: INote[];
}
function Notes({ data }: NotesProps) {
const [dialogOpen, setDialogOpen] = useState(false);
const [notes, setNotes] = useState<INote[]>(data);
const { register, handleSubmit } = useForm<AddNoteType>({
resolver: zodResolver(addNoteSchema),
});
const addNoteSubmit = (data: AddNoteType) => {
console.log(data)
const search = useDebounce(async (input: string) => {
try {
const res = await api.notes.getAllNotes(input);
setNotes(res);
} catch (err) {
console.error(err);
toast.error('A error occurred while searching');
}
}, 500);
const addNoteSubmit = async (data: AddNoteType) => {
try {
const res = await api.notes.uploadSingleNote(data);
setNotes(old => [res, ...old]);
} catch (err) {
console.error(err);
toast.error('Error Uploading Data');
} finally {
setDialogOpen(false);
}
};
const onDeleteNote = async (noteID: string) => {
try {
api.notes.deleteSingleNote({ noteID });
setNotes(old => old.filter(note => note.gistID !== noteID));
} catch (err) {
console.error(err);
toast.error('Error Deleting Note');
}
};
const openAddNoteDialog = () => {
setDialogOpen(true)
};
const closeAddNoteDialog = () => {
setDialogOpen(false)
setDialogOpen(true);
};
return (
<>
<div className="bg-gray-900 p-5 flex items-center w-full gap-2 my-10">
<Input type={'text'} placeholder="Search Notes" className="flex-1" />
<Button size="icon">
<Input
onChange={evt => search(evt.target.value)}
type={'text'}
placeholder="Search Notes"
className="flex-1"
/>
{/* <Button size="icon">
<SearchIcon className="h-4 w-4" />
</Button>
</Button> */}
<Button onClick={openAddNoteDialog} title="Add a note" size="icon">
<PlusIcon className="h-4 w-4" />
</Button>
</div>
<NotesList />
<dialog
className={cn("fixed top-0 left-0 w-screen h-screen flex justify-center items-center bg-transparent",!dialogOpen?"hidden":"")}
ref={dialogRef}
onClick={closeAddNoteDialog}
>
<NotesList data={notes} onDeleteNote={onDeleteNote} />
<Modal open={dialogOpen} onClose={() => setDialogOpen(false)}>
<form
onClick={(e) => {e.stopPropagation();}}
onClick={e => {
e.stopPropagation();
}}
onSubmit={handleSubmit(addNoteSubmit)}
className="w-full max-w-xl bg-gray-800 text-primary p-5 rounded flex flex-col gap-2"
>
<TextArea
{...register('text')}
{...register('content')}
placeholder="Write your text ..."
withLabel={true}
label="Text"
/>
<Input
{...register('password')}
{...register('passkey')}
placeholder="Password"
withLabel={true}
label="Password"
@ -68,25 +99,23 @@ function Notes() {
<div className="relative flex items-start">
<div className="flex h-6 items-center">
<input
{...register('burn')}
{...register('isOneTimeOnly')}
id="burn"
type="checkbox"
className="h-4 w-4 rounded border-primary bg-transparent text-primary"
/>
</div>
<div className="ml-3 text-sm leading-6">
<label htmlFor="burn" className="font-medium text-primary">
Burn
</label>
<label htmlFor="burn" className="ml-3 text-sm leading-6">
<p className="font-medium text-primary">Burn</p>
<p id="offers-description" className="text-white">
Burn the note after first use
</p>
</div>
</label>
</div>
<Button>Add Note</Button>
</form>
</dialog>
</Modal>
</>
);
}

View File

@ -0,0 +1,15 @@
export const useDebounce = (
callback: (param: any) => void | Promise<void>,
gap: number
) => {
let timeout: NodeJS.Timeout | null = null;
return (param: any) => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
callback(param);
}, gap);
};
};

View File

@ -0,0 +1,8 @@
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>;

View File

@ -1,9 +1,9 @@
import { z } from 'zod';
export const addNoteSchema = z.object({
text: z.string().min(1),
password: z.string(),
burn: z.boolean(),
content: z.string().min(1),
passkey: z.string().optional(),
isOneTimeOnly: z.boolean(),
});
export type AddNoteType = z.infer<typeof addNoteSchema>;

View File

@ -23,8 +23,9 @@
"clsx": "^1.2.1",
"eslint": "8.41.0",
"eslint-config-next": "13.4.5",
"js-cookie": "^3.0.5",
"lucide-react": "^0.221.0",
"next": "13.4.3",
"next": "^13.4.7",
"next-auth": "^4.22.1",
"postcss": "8.4.24",
"react": "18.2.0",
@ -44,6 +45,7 @@
"@storybook/nextjs": "^7.0.22",
"@storybook/react": "^7.0.17",
"@storybook/testing-library": "^0.0.14-next.2",
"@types/js-cookie": "^3.0.3",
"eslint-plugin-storybook": "^0.6.12",
"storybook": "^7.0.17"
}

4
packages/dashboard/types/apiKey.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
interface IApiKey {
key: string;
keyID: string;
}

View File

@ -0,0 +1,5 @@
export interface UploadsListComponentProps {
edit: boolean;
data: IFile[];
onDelete:(fileID:string,deleteToken:string) => void;
}

9
packages/dashboard/types/notes.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
interface INote {
gistID: string;
content: string;
gist_url_key: string;
created_on: string;
isPrivate: boolean;
isOneTimeOnly: boolean;
views: number;
}

View File

@ -0,0 +1,6 @@
interface ISettings {
theme: string;
language: string;
imageExtensions: string[];
fileExtensions: string[];
}

6
packages/dashboard/types/url.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
interface IUrl {
clicks: number;
original_url: string;
short_key: string;
urlID: string;
}

132
yarn.lock
View File

@ -2290,6 +2290,18 @@
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4":
<<<<<<< HEAD
version "7.22.3"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz"
integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9":
version "7.21.9"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz"
integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==
=======
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
@ -2300,6 +2312,7 @@
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==
>>>>>>> dev
dependencies:
"@babel/code-frame" "^7.22.5"
"@babel/parser" "^7.22.5"
@ -3434,10 +3447,17 @@
pump "^3.0.0"
tar-fs "^2.1.1"
<<<<<<< HEAD
"@next/env@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.7.tgz#ca12d341edb128ca70384635bd2794125ffb1c01"
integrity sha512-ZlbiFulnwiFsW9UV1ku1OvX/oyIPLtMk9p/nnvDSwI0s7vSoZdRtxXNsaO+ZXrLv/pMbXVGq4lL8TbY9iuGmVw==
=======
"@next/env@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.3.tgz#cb00bdd43a0619a79a52c9336df8a0aa84f8f4bf"
integrity sha512-pa1ErjyFensznttAk3EIv77vFbfSYT6cLzVRK5jx4uiRuCQo+m2wCFAREaHKIy63dlgvOyMlzh6R8Inu8H3KrQ==
>>>>>>> dev
"@next/eslint-plugin-next@13.4.5":
version "13.4.5"
@ -3446,26 +3466,37 @@
dependencies:
glob "7.1.7"
"@next/swc-darwin-arm64@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz#2d6c99dd5afbcce37e4ba0f64196317a1259034d"
integrity sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==
"@next/swc-darwin-arm64@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.7.tgz#5e36c26dda5b0bc0ea15d8555d0abd71a1ef4b5d"
integrity sha512-VZTxPv1b59KGiv/pZHTO5Gbsdeoxcj2rU2cqJu03btMhHpn3vwzEK0gUSVC/XW96aeGO67X+cMahhwHzef24/w==
"@next/swc-darwin-x64@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz#162b15fb8a54d9f64e69c898ebeb55b7dac9bddd"
integrity sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==
"@next/swc-darwin-x64@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.7.tgz#4c14ec14b200373cd602589086cb1253a28cd803"
integrity sha512-gO2bw+2Ymmga+QYujjvDz9955xvYGrWofmxTq7m70b9pDPvl7aDFABJOZ2a8SRCuSNB5mXU8eTOmVVwyp/nAew==
"@next/swc-linux-arm64-gnu@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz#aee57422f11183d6a2e4a2e8aa23b9285873e18f"
integrity sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==
"@next/swc-linux-arm64-gnu@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.7.tgz#e7819167ec876ddac5a959e4c7bce4d001f0e924"
integrity sha512-6cqp3vf1eHxjIDhEOc7Mh/s8z1cwc/l5B6ZNkOofmZVyu1zsbEM5Hmx64s12Rd9AYgGoiCz4OJ4M/oRnkE16/Q==
"@next/swc-linux-arm64-musl@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz#c10b6aaaa47b341c6c9ea15f8b0ddb37e255d035"
integrity sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==
"@next/swc-linux-arm64-musl@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.7.tgz#0cac0f01d4e308b439e6c33182bed77835fe383b"
integrity sha512-T1kD2FWOEy5WPidOn1si0rYmWORNch4a/NR52Ghyp4q7KyxOCuiOfZzyhVC5tsLIBDH3+cNdB5DkD9afpNDaOw==
<<<<<<< HEAD
"@next/swc-linux-x64-gnu@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.7.tgz#feb61e16a68c67f3ef230f30d9562a3783c7bd59"
integrity sha512-zaEC+iEiAHNdhl6fuwl0H0shnTzQoAoJiDYBUze8QTntE/GNPfTYpYboxF5LRYIjBwETUatvE0T64W6SKDipvg==
"@next/swc-linux-x64-musl@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.7.tgz#02179ecfa6d24a2956c2b54f7d27a050568bbf24"
integrity sha512-X6r12F8d8SKAtYJqLZBBMIwEqcTRvUdVm+xIq+l6pJqlgT2tNsLLf2i5Cl88xSsIytBICGsCNNHd+siD2fbWBA==
=======
"@next/swc-linux-x64-gnu@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz#3f85bc5591c6a0d4908404f7e88e3c04f4462039"
@ -3475,21 +3506,22 @@
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz#f4535adc2374a86bc8e43af149b551567df065de"
integrity sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==
>>>>>>> dev
"@next/swc-win32-arm64-msvc@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz#e76106d85391c308c5ed70cda2bca2c582d65536"
integrity sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==
"@next/swc-win32-arm64-msvc@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.7.tgz#274b7f00a2ec5934af73db15da8459e8647bfaed"
integrity sha512-NPnmnV+vEIxnu6SUvjnuaWRglZzw4ox5n/MQTxeUhb5iwVWFedolPFebMNwgrWu4AELwvTdGtWjqof53AiWHcw==
"@next/swc-win32-ia32-msvc@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz#8eb5d9dd71ed7a971671291605ad64ad522fb3bc"
integrity sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==
"@next/swc-win32-ia32-msvc@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.7.tgz#4a95c106a6db2eee3a4c1352b77995e298d7446a"
integrity sha512-6Hxijm6/a8XqLQpOOf/XuwWRhcuc/g4rBB2oxjgCMuV9Xlr2bLs5+lXyh8w9YbAUMYR3iC9mgOlXbHa79elmXw==
"@next/swc-win32-x64-msvc@13.4.3":
version "13.4.3"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.3.tgz#c7b2b1b9e158fd7749f8209e68ee8e43a997eb4c"
integrity sha512-jglUk/x7ZWeOJWlVoKyIAkHLTI+qEkOriOOV+3hr1GyiywzcqfI7TpFSiwC7kk1scOiH7NTFKp8mA3XPNO9bDw==
"@next/swc-win32-x64-msvc@13.4.7":
version "13.4.7"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.7.tgz#5137780f58d7f0230adc293a0429821bfa7d8c21"
integrity sha512-sW9Yt36Db1nXJL+mTr2Wo0y+VkPWeYhygvcHj1FF0srVtV+VoDjxleKtny21QHaG05zdeZnw2fCtf2+dEqgwqA==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@ -5067,6 +5099,11 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/js-cookie@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.3.tgz#d6bfbbdd0c187354ca555213d1962f6d0691ff4e"
integrity sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==
"@types/js-yaml@^4.0.0":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138"
@ -5206,6 +5243,9 @@
dependencies:
"@types/react" "*"
<<<<<<< HEAD
"@types/react@*", "@types/react@18.2.7", "@types/react@>=16":
=======
"@types/react@*", "@types/react@>=16":
version "18.2.13"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.13.tgz#a98c09bde8b18f80021935b11d2d29ef5f4dcb2f"
@ -5216,6 +5256,7 @@
csstype "^3.0.2"
"@types/react@18.2.7":
>>>>>>> dev
version "18.2.7"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.7.tgz#dfb4518042a3117a045b8c222316f83414a783b3"
integrity sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==
@ -10255,6 +10296,11 @@ jpeg-js@^0.4.4:
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==
js-cookie@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -11174,28 +11220,36 @@ next-auth@^4.22.1:
preact-render-to-string "^5.1.19"
uuid "^8.3.2"
<<<<<<< HEAD
next@^13.4.7:
version "13.4.7"
resolved "https://registry.yarnpkg.com/next/-/next-13.4.7.tgz#2ab20e6fada2e25cb81bd17f68956705ffd9824e"
integrity sha512-M8z3k9VmG51SRT6v5uDKdJXcAqLzP3C+vaKfLIAM0Mhx1um1G7MDnO63+m52qPdZfrTFzMZNzfsgvm3ghuVHIQ==
=======
next@13.4.3:
version "13.4.3"
resolved "https://registry.yarnpkg.com/next/-/next-13.4.3.tgz#7f417dec9fa2731d8c1d1819a1c7d0919ad6fc75"
integrity sha512-FV3pBrAAnAIfOclTvncw9dDohyeuEEXPe5KNcva91anT/rdycWbgtu3IjUj4n5yHnWK8YEPo0vrUecHmnmUNbA==
>>>>>>> dev
dependencies:
"@next/env" "13.4.3"
"@next/env" "13.4.7"
"@swc/helpers" "0.5.1"
busboy "1.6.0"
caniuse-lite "^1.0.30001406"
postcss "8.4.14"
styled-jsx "5.1.1"
watchpack "2.4.0"
zod "3.21.4"
optionalDependencies:
"@next/swc-darwin-arm64" "13.4.3"
"@next/swc-darwin-x64" "13.4.3"
"@next/swc-linux-arm64-gnu" "13.4.3"
"@next/swc-linux-arm64-musl" "13.4.3"
"@next/swc-linux-x64-gnu" "13.4.3"
"@next/swc-linux-x64-musl" "13.4.3"
"@next/swc-win32-arm64-msvc" "13.4.3"
"@next/swc-win32-ia32-msvc" "13.4.3"
"@next/swc-win32-x64-msvc" "13.4.3"
"@next/swc-darwin-arm64" "13.4.7"
"@next/swc-darwin-x64" "13.4.7"
"@next/swc-linux-arm64-gnu" "13.4.7"
"@next/swc-linux-arm64-musl" "13.4.7"
"@next/swc-linux-x64-gnu" "13.4.7"
"@next/swc-linux-x64-musl" "13.4.7"
"@next/swc-win32-arm64-msvc" "13.4.7"
"@next/swc-win32-ia32-msvc" "13.4.7"
"@next/swc-win32-x64-msvc" "13.4.7"
nextgen-events@^1.3.4:
version "1.5.3"
@ -14402,7 +14456,7 @@ walker@^1.0.8:
dependencies:
makeerror "1.0.12"
watchpack@^2.2.0, watchpack@^2.4.0:
watchpack@2.4.0, watchpack@^2.2.0, watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==