Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add integration of the WebArchive #62

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
23 changes: 23 additions & 0 deletions app/lookup/[domain]/archive/_components/CreateSnapshotCTA.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

import { useRouter } from 'next/navigation';
import { FC, ReactElement } from 'react';

import { Button } from '@/components/ui/button';

interface Props {
domain: string;
}

export const CreateSnapshotCTA: FC<Props> = (domain): ReactElement => {
const router = useRouter();

return (
<Button
variant="outline"
onClick={() => router.push(`https://web.archive.org/save/${domain}`)}
>
Create snapshot
</Button>
);
};
83 changes: 83 additions & 0 deletions app/lookup/[domain]/archive/_components/WebArchiveIframeDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use client';

import { ShieldHalf } from 'lucide-react';
import { DateTime } from 'luxon';
import Link from 'next/link';
import { type FC, ReactElement, useState } from 'react';
import Iframe from 'react-iframe';

import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
DrawerClose,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
} from '@/components/ui/drawer';
import { Skeleton } from '@/components/ui/skeleton';

type WebArchiveIframeDrawerProps = {
url: string;
timestamp: string;
};

const WebArchiveIframeDrawer: FC<WebArchiveIframeDrawerProps> = ({
url,
timestamp,
}): ReactElement => {
const [isIframeLoaded, setIsIframeLoaded] = useState(false);

return (
<>
<DrawerHeader>
<DrawerTitle>
<span className="font-extrabold">WebArchive:</span>{' '}
{DateTime.fromFormat(timestamp, 'yyyyMMddHHmmss').toFormat(
'MM/dd/yyyy tt'
)}
</DrawerTitle>
<DrawerDescription>
<span>{url}</span>
<Badge variant="outline" className="ml-2">
<ShieldHalf className="mr-1 inline-block h-3 w-3" />
Secure
</Badge>
</DrawerDescription>
</DrawerHeader>
<div className="h-full p-4 pb-0">
<Iframe
url={url}
className="h-full w-full overflow-hidden rounded-lg"
styles={{ display: isIframeLoaded ? 'block' : 'none' }}
onLoad={() => {
setIsIframeLoaded(true);
}}
/>
{!isIframeLoaded && (
<Skeleton className="flex h-full w-full items-center justify-center rounded-lg">
<span className="flex flex-col text-center text-gray-900 dark:text-gray-200">
<span>Please wait</span>
<span>This could take a while ...</span>
</span>
</Skeleton>
)}
</div>
<DrawerFooter className="flex w-full flex-row">
<DrawerClose className="w-full" asChild>
<Button variant="outline">Close</Button>
</DrawerClose>
<Link
href={url}
target="_blank"
rel="noopener noreffrer"
className="w-full lg:w-3/6"
>
<Button className="w-full">Open in WebArchive</Button>
</Link>
</DrawerFooter>
</>
);
};

export default WebArchiveIframeDrawer;
67 changes: 67 additions & 0 deletions app/lookup/[domain]/archive/_components/WebArchiveItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';

import { DateTime } from 'luxon';
import Link from 'next/link';
import { type FC, ReactElement, useEffect, useState } from 'react';
import Iframe from 'react-iframe';

import { Button } from '@/components/ui/button';
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';
import { Skeleton } from '@/components/ui/skeleton';

import WebArchiveIframeDrawer from '@/app/lookup/[domain]/archive/_components/WebArchiveIframeDrawer';

type WebArchiveItemProps = {
url: string;
timestamp: string;
};

const WebArchiveItem: FC<WebArchiveItemProps> = ({
url,
timestamp,
}): ReactElement => {
const [isIframeLoaded, setIsIframeLoaded] = useState(false);

return (
<div className="col-span-1 flex flex-col gap-4 rounded-xl border p-3">
<Iframe
url={url}
className="h-48 w-full overflow-hidden rounded-lg"
styles={{ display: isIframeLoaded ? 'block' : 'none' }}
scrolling="no"
onLoad={() => {
setIsIframeLoaded(true);
}}
/>
{!isIframeLoaded && <Skeleton className="h-48 w-full rounded-lg" />}
<div className="flex flex-row justify-between">
<span className="text-sm">
{DateTime.fromFormat(timestamp, 'yyyyMMddHHmmss').toFormat(
'MM/dd/yyyy mm:ss tt'
)}
</span>
<Drawer>
<DrawerTrigger asChild>
<Button variant="ghost" size="sm">
Open
</Button>
</DrawerTrigger>
<DrawerContent className="h-5/6">
<WebArchiveIframeDrawer url={url} timestamp={timestamp} />
</DrawerContent>
</Drawer>
</div>
</div>
);
};

export default WebArchiveItem;
24 changes: 24 additions & 0 deletions app/lookup/[domain]/archive/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import { type FC, ReactElement, useEffect } from 'react';

type ArchiveErrorProps = {
error: Error & { digest?: string };
reset: () => void;
};

const ArchiveError: FC<ArchiveErrorProps> = ({ error }): ReactElement => {
useEffect(() => {
console.error(error);
}, [error]);

return (
<div className="mt-12 flex flex-col items-center gap-2">
<h2>Something went wrong!</h2>
<p className="mt-2 text-center text-sm text-muted-foreground">
Digest: {error.digest}
</p>
</div>
);
};
export default ArchiveError;
14 changes: 14 additions & 0 deletions app/lookup/[domain]/archive/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FC, ReactElement } from 'react';

import { Skeleton } from '@/components/ui/skeleton';

const ArchiveLoading: FC = (): ReactElement => (
<div className="mt-12">
<Skeleton className="mb-4 mt-8 h-9 w-48 rounded-sm" />
{Array.from({ length: 10 }).map((_, i) => (
<Skeleton className="my-3 h-5 w-full rounded-sm" key={i} />
))}
</div>
);

export default ArchiveLoading;
175 changes: 175 additions & 0 deletions app/lookup/[domain]/archive/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { CircleOff } from 'lucide-react';
import { DateTime } from 'luxon';
import { Metadata } from 'next';
import { type FC, ReactElement } from 'react';

import { CreateSnapshotCTA } from '@/app/lookup/[domain]/archive/_components/CreateSnapshotCTA';
import WebArchiveItem from '@/app/lookup/[domain]/archive/_components/WebArchiveItem';
import StyledError from '@/components/StyledError';

type ArchivePageProps = {
params: {
domain: string;
};
};

export const generateMetadata = ({
params: { domain },
}: ArchivePageProps): Metadata => ({
openGraph: {
url: `/lookup/${domain}/archive`,
},
alternates: {
canonical: `/lookup/${domain}/archive`,
},
});

const ArchivePage: FC<ArchivePageProps> = async ({
params: { domain },
}): Promise<ReactElement> => {
// fetch latest snapshot by WebArchive
const latestSnapshotFetch = await fetch(
`https://archive.org/wayback/available?url=${domain}`
);
const latestSnapshot = await latestSnapshotFetch.json();

console.log(latestSnapshot);

async function fetchData(
timestamp,
resultArray,
domain,
callback,
gotDifferenceInMonths
) {
const formattedTimestamp = DateTime.fromFormat(
timestamp,
'yyyyMMddHHmmss'
).toFormat('yyyyMMddHHmmss');
const url = `https://archive.org/wayback/available?url=${domain}&timestamp=${formattedTimestamp}`;

try {
const latestSnapshotFetch = await fetch(url);
const responseData = await latestSnapshotFetch.json();

if (
responseData.archived_snapshots &&
responseData.archived_snapshots.closest
) {
const closestTimestamp =
responseData.archived_snapshots.closest.timestamp;

if (responseData.archived_snapshots.closest.timestamp === timestamp) {
if (gotDifferenceInMonths > 48) return;

const nextTimestamp = DateTime.fromFormat(
closestTimestamp,
'yyyyMMddHHmmss'
).minus({ months: gotDifferenceInMonths * 2 });

const formattedNextTimestamp =
nextTimestamp.toFormat('yyyyMMddHHmmss');

await fetchData(
formattedNextTimestamp,
resultArray,
domain,
callback,
gotDifferenceInMonths * 2
);
}

resultArray.push(responseData);

if (callback) {
callback(resultArray);
}

// Berechnen Sie den nächsten Timestamp basierend auf der Differenz zum vorherigen
let nextTimestamp;
const differenceInMonths = DateTime.fromFormat(
closestTimestamp,
'yyyyMMddHHmmss'
).diff(
DateTime.fromFormat(timestamp, 'yyyyMMddHHmmss'),
'months'
).months;

if (differenceInMonths > -6) {
// Wenn die Differenz größer als -6 Monate ist, setze den nächsten Timestamp auf -6 Monate
nextTimestamp = DateTime.fromFormat(
closestTimestamp,
'yyyyMMddHHmmss'
).minus({ months: 6 });
} else {
// Andernfalls setze den nächsten Timestamp auf -1 Monat
nextTimestamp = DateTime.fromFormat(
closestTimestamp,
'yyyyMMddHHmmss'
).minus({ months: 1 });
}

const formattedNextTimestamp = nextTimestamp.toFormat('yyyyMMddHHmmss');

await fetchData(
formattedNextTimestamp,
resultArray,
domain,
callback,
differenceInMonths
);
} else {
// Stoppen Sie die Rekursion, wenn keine älteren Snapshots mehr gefunden werden
console.log('Keine älteren Snapshots gefunden.');
}
} catch (error) {
// Fehlerbehandlung hier, wenn die Anfrage fehlschlägt
console.error('Fehler bei der Anfrage:', error);
}
}

const resultArray = [];

fetchData(
latestSnapshot.archived_snapshots.closest.timestamp,
resultArray,
domain,
(finalResult) => {
console.log('Endgültiges Ergebnis:', finalResult);
},
1
);

if (!latestSnapshot.archived_snapshots.closest) {
return (
<div className="flex h-full w-full">
<div className="m-auto">
<StyledError
title="This domain has not been archived yet."
description="This domain has not been archived yet. Create a snapshot to archive this domain."
icon={<CircleOff className="h-16 w-16" />}
/>
<div className="mt-3 flex flex-row justify-center">
<CreateSnapshotCTA domain={domain} />
</div>
</div>
</div>
);
}

//TODO: fetch older snapshots by WebArchive

return (
<>
<CreateSnapshotCTA domain={domain} />
<div className="mt-10 grid grid-cols-2 gap-4 lg:grid-cols-4">
<WebArchiveItem
url={latestSnapshot.archived_snapshots.closest.url}
timestamp={latestSnapshot.archived_snapshots.closest.timestamp}
/>
</div>
</>
);
};

export default ArchivePage;
Loading
Loading