Skip to content

Commit

Permalink
feat: support batch archive
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronConlon committed Jul 27, 2024
1 parent 9861184 commit 442462a
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 103 deletions.
23 changes: 19 additions & 4 deletions apps/v-next/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,29 @@ export default function ConfirmModal({ children, confirmFc, title, loading }: Co
<div className="p-6 w-[300px] text-center">Are you sure?</div>
<div className="mt-4 flex justify-center gap-2 items-center">
<Button variant="outline" size="sm" onClick={close}>
Cancel
No
</Button>
<Button onClick={confirmFc} size="sm" disabled={loading} loading={loading}>
Confirm
<Button
onClick={() => {
close();
confirmFc();
}}
size="sm"
disabled={loading}
loading={loading}
>
Yes
</Button>
</div>
</Modal>
<button onClick={open}>{children}</button>
<button
onClick={(e) => {
e.stopPropagation();
open();
}}
>
{children}
</button>
</>
);
}
69 changes: 69 additions & 0 deletions apps/v-next/components/query/ArchiveModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use client';
import { Tooltip } from '@mantine/core';
import { cn } from '@shared/fc';
import { GithubAPI, IUserRepoList } from '@shared/github-api';
import { useRequest } from 'ahooks';
import { ArchiveIcon } from 'lucide-react';
import toast from 'react-hot-toast';
import ConfirmModal from '../ConfirmModal';

interface ArchiveModalProps {
selectedRows: IUserRepoList;
mutate: (items: any) => void;
accessToken: string;
}

export default function ArchiveModal({ selectedRows, mutate, accessToken }: ArchiveModalProps) {
const { run, loading } = useRequest(
() => {
return Promise.allSettled(
selectedRows
.filter((i) => i.archived !== true)
.map((i) =>
GithubAPI.repo
.patchRepo({
auth: accessToken,
owner: i.owner.login,
repo: i.name,
schema: {
archived: true
}
})
.then(() => {
toast.success(`Archived ${i.full_name}`);
mutate((prevData: any) => {
return prevData.map((item: any) => {
if (item.id === i.id) {
return {
...item,
archived: true
};
}
return item;
});
});
})
.catch(() => {
toast.error(`Failed to archived ${i.full_name}`);
})
)
);
},
{
manual: true
}
);

return (
<ConfirmModal title="Archive your selected repositories?" confirmFc={run} loading={loading}>
<Tooltip label="batch archive">
<ArchiveIcon
size={22}
className={cn('text-orange-500 mt-[5px]', {
hidden: selectedRows.filter((i) => i.archived !== true).length === 0
})}
/>
</Tooltip>
</ConfirmModal>
);
}
117 changes: 42 additions & 75 deletions apps/v-next/components/query/DeleteModal.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,55 @@
'use client';
import { Button, List, Modal, rem, ThemeIcon } from '@mantine/core';
import { Button, List, Modal, rem, ThemeIcon, Tooltip } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { cn } from '@shared/fc';
import { GithubAPI, IUserRepoList } from '@shared/github-api';
import { useRequest } from 'ahooks';
import { CheckCircle, RefreshCcw, Trash2 } from 'lucide-react';
import { useState } from 'react';
import { RefreshCcw, Trash2 } from 'lucide-react';
import toast from 'react-hot-toast';

interface DeleteModalProps {
selectedRows: IUserRepoList;
onSuccess: (ids: number[]) => void;
removeItem: (id: number) => void;
accessToken: string;
}

export default function DeleteModal({ selectedRows, onSuccess, accessToken }: DeleteModalProps) {
export default function DeleteModal({ selectedRows, removeItem, accessToken }: DeleteModalProps) {
const [opened, { open, close }] = useDisclosure(false);
const [deletedIds, setDeletedIds] = useState<number[]>([]);

const { run, loading, cancel } = useRequest(
() => {
if (selectedRows.length === 0) {
close();
}

return Promise.allSettled(
selectedRows
.filter((i) => !deletedIds.includes(i.id))
.map((i) =>
GithubAPI.repo
.removeRepo({
auth: accessToken,
owner: i.owner.login,
repo: i.name
})
.then(() => {
setDeletedIds((prev) => {
if (prev.includes(i.id)) {
return prev;
}
return [...prev, i.id];
});
})
.catch(() => {
toast.error(`Failed to delete ${i.full_name}`);
})
)
selectedRows.map((i) =>
GithubAPI.repo
.removeRepo({
auth: accessToken,
owner: i.owner.login,
repo: i.name
})
.then(() => {
toast.success(`Deleted ${i.full_name}`);
removeItem(i.id);
})
.catch(() => {
toast.error(`Failed to delete ${i.full_name}`);
})
)
);
},
{
manual: true,
onSuccess: () => {
console.log('deleted:', deletedIds);
onSuccess(deletedIds);
close();
}
}
);

const onClose = () => {
cancel();
setDeletedIds([]);
close();
};

Expand Down Expand Up @@ -85,24 +79,7 @@ export default function DeleteModal({ selectedRows, onSuccess, accessToken }: De
}
>
{selectedRows.map((repo) => (
<List.Item
key={repo.id}
icon={
deletedIds.includes(repo.id) ? (
<ThemeIcon color="red" size={24} radius="xl">
<CheckCircle style={{ width: rem(16), height: rem(16) }} />
</ThemeIcon>
) : undefined
}
>
<span
className={cn({
'line-through': deletedIds.includes(repo.id)
})}
>
{repo.full_name}
</span>
</List.Item>
<List.Item key={repo.id}>{repo.full_name}</List.Item>
))}
</List>
</div>
Expand All @@ -114,40 +91,30 @@ export default function DeleteModal({ selectedRows, onSuccess, accessToken }: De
>
Restore tips
</a>
<Button size="sm" onClick={onClose}>
{deletedIds.map((i) => i.toString()).join('-') ===
selectedRows.map((i) => i.id.toString()).join('-')
? 'Close'
: 'Cancel'}
<Button size="sm" onClick={onClose} variant="outline">
Cancel
</Button>
<Button
onClick={run}
size="sm"
variant="outline"
color="red"
disabled={
deletedIds.map((i) => i.toString()).join('-') ===
selectedRows.map((i) => i.id.toString()).join('-')
}
>
<Button onClick={run} size="sm" color="red">
Delete
</Button>
</div>
</div>
</Modal>

<button
onClick={() => {
if (selectedRows.length) {
open();
}
}}
className={cn('text-red-500 pt-1 flex gap-1 items-center', {
'opacity-0': selectedRows.length === 0
})}
>
<Trash2 size={22} />
</button>
<Tooltip label="batch delete">
<button
onClick={() => {
if (selectedRows.length) {
open();
}
}}
className={cn('text-red-500 pt-1 flex gap-1 items-center', {
'opacity-0': selectedRows.length === 0
})}
>
<Trash2 size={22} />
</button>
</Tooltip>
</>
);
}
55 changes: 31 additions & 24 deletions apps/v-next/components/query/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';
import toast from 'react-hot-toast';
import ConfirmModal from '../ConfirmModal';
import ArchiveModal from './ArchiveModal';
import DeleteModal from './DeleteModal';
import DescriptionModal from './DescriptionModal';

Expand Down Expand Up @@ -112,7 +113,7 @@ export default function Query({ accessToken }: IQueryProps) {
})
.then(() => {
toast.success('Change the repository privacy successfully');
mutate(data!.map((i) => (i.id === repository.id ? { ...i, private: !i.private } : i)));
mutate((data) => data!.map((i) => (i.id === repository.id ? { ...i, private: !i.private } : i)));
});
} catch (error) {
// error info
Expand All @@ -125,19 +126,16 @@ export default function Query({ accessToken }: IQueryProps) {
try {
if (repository.archived === archived) return;
const { name, owner } = repository;
await GithubAPI.repo
.patchRepo({
auth: accessToken,
repo: name,
owner: owner.login,
schema: {
archived: !repository.archived
}
})
.then(() => {
toast.success('Change the repository privacy successfully');
mutate(data!.map((i) => (i.id === repository.id ? { ...i, archived: !i.archived } : i)));
});
await GithubAPI.repo.patchRepo({
auth: accessToken,
repo: name,
owner: owner.login,
schema: {
archived: !repository.archived
}
});
toast.success('Change the repository privacy successfully');
mutate((data) => data!.map((i) => (i.id === repository.id ? { ...i, archived: !i.archived } : i)));
} catch (error) {
// error info
console.error(error);
Expand Down Expand Up @@ -189,24 +187,33 @@ export default function Query({ accessToken }: IQueryProps) {
<Search size={16} className="mr-1" />
Search
</Button>
{/* it's debug code... */}
<Button variant="outline" onClick={generateTempRepo} className="!hidden">
Create temp repos
</Button>
</form>

<div className="my-4 relative bg-white">
{data && !loading ? (
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>
<DeleteModal
accessToken={accessToken}
selectedRows={data.filter((i) => selectedRows.includes(i.id))}
onSuccess={(ids: number[]) => {
setSelectedRows((prev) => prev.filter((i) => !ids.includes(i)));
mutate(data.filter((i) => !ids.includes(i.id)));
}}
/>
<div className="flex items-center min-w-12">
<DeleteModal
accessToken={accessToken}
selectedRows={data.filter((i) => selectedRows.includes(i.id))}
removeItem={(id: number) => {
setSelectedRows(selectedRows.filter((i) => i !== id));
mutate((data) => data!.filter((i) => i.id !== id));
}}
/>
<ArchiveModal
accessToken={accessToken}
selectedRows={data.filter((i) => selectedRows.includes(i.id))}
mutate={mutate}
/>
</div>
</Table.Th>
<Table.Th>Full Name</Table.Th>
<Table.Th>Private</Table.Th>
Expand Down Expand Up @@ -295,7 +302,7 @@ export default function Query({ accessToken }: IQueryProps) {
{!archived && (
<Switch
onLabel="YES"
defaultChecked={isPrivate}
checked={isPrivate}
disabled={archived}
onChange={(e) => {
changeRepoIsPrivate(data.find((i) => i.id === id)!, e.target.checked);
Expand All @@ -306,7 +313,7 @@ export default function Query({ accessToken }: IQueryProps) {
<Table.Td>
<Switch
onLabel="YES"
defaultChecked={archived}
checked={archived}
onChange={(e) => {
changeRepoArchived(data.find((i) => i.id === id)!, e.target.checked);
}}
Expand Down

0 comments on commit 442462a

Please sign in to comment.