Skip to content

Commit

Permalink
feat: add detail page count and link
Browse files Browse the repository at this point in the history
  • Loading branch information
theodorusclarence committed Jan 19, 2022
1 parent c11ba88 commit 1493b46
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 23 deletions.
27 changes: 26 additions & 1 deletion .vscode/typescriptreact.code-snippets
Expand Up @@ -7,7 +7,7 @@
"React.useState": {
"prefix": "us",
"body": [
"const [${1}, set${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}] = React.useState<$1>(${2:initial${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}})$0"
"const [${1}, set${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}] = React.useState<$3>(${2:initial${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}})$0"
]
},
"React.useEffect": {
Expand Down Expand Up @@ -39,6 +39,22 @@
"}"
]
},
"React Functional Component with Props": {
"prefix": "rcp",
"body": [
"import * as React from 'react';\n",
"import clsxm from '@/lib/clsxm';\n",
"type ${1:${TM_FILENAME_BASE}}Props= {\n",
"} & React.ComponentPropsWithoutRef<'div'>\n",
"export default function ${1:${TM_FILENAME_BASE}}({className, ...rest}: ${1:${TM_FILENAME_BASE}}Props) {",
" return (",
" <div className={clsxm('', className)} {...rest}>",
" $0",
" </div>",
" )",
"}"
]
},
//#endregion //*======== React ===========

//#region //*=========== Commons ===========
Expand All @@ -51,6 +67,15 @@
"//#endregion //*======== ${1} ==========="
]
},
"Region CSS": {
"prefix": "regc",
"scope": "css, scss",
"body": [
"/* #region /**=========== ${1} =========== */",
"${TM_SELECTED_TEXT}$0",
"/* #endregion /**======== ${1} =========== */"
]
},
//#endregion //*======== Commons ===========

//#region //*=========== Nextjs ===========
Expand Down
9 changes: 3 additions & 6 deletions next.config.js
Expand Up @@ -6,12 +6,9 @@ module.exports = {

reactStrictMode: true,

// Uncoment to add domain whitelist
// images: {
// domains: [
// 'res.cloudinary.com',
// ],
// },
images: {
domains: ['icons.duckduckgo.com'],
},

// SVGR
webpack(config) {
Expand Down
27 changes: 27 additions & 0 deletions src/components/Favicon.tsx
@@ -0,0 +1,27 @@
import * as React from 'react';

import clsxm from '@/lib/clsxm';

import NextImage, { NextImageProps } from '@/components/NextImage';

type FaviconProps = {
fullUrl: string;
} & Omit<NextImageProps, 'src' | 'alt' | 'width' | 'height'>;

export default function Favicon({ className, fullUrl, ...rest }: FaviconProps) {
const FAVICON_URL = 'https://icons.duckduckgo.com/ip3/';
const { hostname } = new URL(fullUrl);

const src = FAVICON_URL + hostname + '.ico';

return (
<NextImage
src={src}
alt={`${hostname} favicon`}
width='20'
height='20'
className={clsxm('', className)}
{...rest}
/>
);
}
2 changes: 1 addition & 1 deletion src/components/NextImage.tsx
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';

import clsxm from '@/lib/clsxm';

type NextImageProps = {
export type NextImageProps = {
useSkeleton?: boolean;
imgClassName?: string;
blurClassName?: string;
Expand Down
47 changes: 41 additions & 6 deletions src/pages/[slug]/detail.tsx
@@ -1,7 +1,14 @@
import { useRouter } from 'next/router';
import * as React from 'react';
import { HiCursorClick } from 'react-icons/hi';
import { useQuery } from 'react-query';

import { trimHttps } from '@/lib/helper';
import { Url } from '@/lib/notion';
import useRQWithToast from '@/hooks/toast/useRQWithToast';

import Accent from '@/components/Accent';
import Favicon from '@/components/Favicon';
import Layout from '@/components/layout/Layout';
import Seo from '@/components/Seo';
import Skeleton from '@/components/Skeleton';
Expand All @@ -18,22 +25,30 @@ export default function DetailPage() {
const [link, setLink] = React.useState<string>();

React.useEffect(() => {
const origin = window.location.origin;
const slug = idParam;

setLink(origin + '/' + slug);
const origin = trimHttps(window.location.href).replace('/detail', '');
setLink(origin);
}, [idParam]);
//#endregion //*======== Link ===========

//#region //*=========== Get Url Data ===========
const { data: url } = useRQWithToast(
useQuery<Url, Error>(`/api/link/${idParam}`, { retry: 1 }),
{
loading: 'Fetching url details...',
success: 'Url detail fetched successfully',
}
);
//#endregion //*======== Get Url Data ===========

return (
<Layout>
<Seo templateTitle='Detail' />

<main>
<section className=''>
<div className='layout flex flex-col items-center py-20 min-h-screen'>
<div className='layout flex flex-col justify-center items-center py-20 min-h-screen'>
<h1 className='h0'>
<Accent>Shorten New Link</Accent>
<Accent>Link Details</Accent>
</h1>

{link ? (
Expand All @@ -47,6 +62,26 @@ export default function DetailPage() {
) : (
<Skeleton className='mt-8 w-72 h-14 rounded' />
)}

<div className='mt-6'>
<h2 className='h4'>Detail</h2>
<div className='flex gap-4 items-center mt-2'>
{url?.link ? (
<Favicon fullUrl={url.link} />
) : (
<Skeleton className='w-5 h-5' />
)}
<div className='w-full max-w-sm font-medium text-gray-300 break-all'>
{url?.link ? url.link : <Skeleton className='w-64 h-5' />}
</div>
</div>
<div className='flex gap-4 items-center mt-2'>
<HiCursorClick className='w-5 h-5' />
<span className='font-medium text-gray-300'>
{url?.count ?? '—'} click{(url?.count ?? 0) > 1 && 's'}
</span>
</div>
</div>
</div>
</section>
</main>
Expand Down
10 changes: 7 additions & 3 deletions src/pages/_middleware.ts
Expand Up @@ -17,16 +17,20 @@ export default async function middleware(req: NextRequest) {
return;
}

const url = await getUrlBySlug(path);

/** Don't redirect if /:slug/detail */
const isDetailPage = req.nextUrl.pathname.split('/')[2]
? req.nextUrl.pathname.split('/')[2] === 'detail'
: false;
if (isDetailPage) {
return;
if (url.link) {
return;
} else {
return NextResponse.redirect('/new?slug=' + path);
}
}

const url = await getUrlBySlug(path);

if (url.link) {
// using fetch because edge function won't allow patch request
await fetch(req.nextUrl.origin + '/api/increment', {
Expand Down
25 changes: 25 additions & 0 deletions src/pages/api/link/[slug].ts
@@ -0,0 +1,25 @@
import { NextApiRequest, NextApiResponse } from 'next';

import { getUrlBySlug } from '@/lib/notion';

export default async function UrlBySlugHandler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'GET') {
const { slug } = req.query;
if (!slug) {
return res.status(400).json({ message: 'Missing slug' });
}

const url = await getUrlBySlug(slug as string);

if (!url.pageId) {
return res.status(404).json({ message: 'Not found' });
}

res.status(200).json(url);
} else {
res.status(405).json({ message: 'Method Not Allowed' });
}
}
31 changes: 25 additions & 6 deletions src/pages/new.tsx
@@ -1,4 +1,5 @@
import axios from 'axios';
import { useRouter } from 'next/router';
import * as React from 'react';
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
Expand All @@ -17,29 +18,47 @@ type NewLinkFormData = {
};

export default function NewLinkPage() {
const router = useRouter();

//#region //*=========== Form ===========
const methods = useForm<NewLinkFormData>({
mode: 'onTouched',
});
const { handleSubmit } = methods;
const { handleSubmit, setValue } = methods;
//#endregion //*======== Form ===========

//#region //*=========== Form Submit ===========
const onSubmit: SubmitHandler<NewLinkFormData> = (data) => {
toast.promise(axios.post('/api/new', data), {
...DEFAULT_TOAST_MESSAGE,
success: 'Link successfully shortened',
});
toast.promise(
axios.post('/api/new', data).then(() => {
router.replace(`/${data.slug}/detail`);
}),
{
...DEFAULT_TOAST_MESSAGE,
success: 'Link successfully shortened',
}
);
};
//#endregion //*======== Form Submit ===========

//#region //*=========== Set Slug Query ===========
React.useEffect(() => {
if (!router.isReady) return;
const query = router.query;

if (query.slug) {
setValue('slug', query.slug as string);
}
}, [router.isReady, router.query, setValue]);
//#endregion //*======== Set Slug Query ===========

return (
<Layout>
<Seo templateTitle='Shorten!' />

<main>
<section>
<div className='layout flex flex-col items-center py-20 min-h-screen'>
<div className='layout flex flex-col justify-center items-center py-20 min-h-screen'>
<h1 className='h0'>
<Accent>Shorten New Link</Accent>
</h1>
Expand Down

1 comment on commit 1493b46

@vercel
Copy link

@vercel vercel bot commented on 1493b46 Jan 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.