-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Shorten links for sharing and convert dialogs to @shadcn/ui (#333)
- Loading branch information
Showing
9 changed files
with
209 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
CWA_API_KEY=YOUR_CWA_API_KEY | ||
NEXTAUTH_SECRET=YOUR_NEXTAUTH_SECRET | ||
SUPABASE_SERVICE_ROLE_KEY=YOUR_SUPABASE_SERVICE_ROLE_KEY | ||
NTHU_OAUTH_SECRET_KEY=<from NTHU OAuth> | ||
CRON_SECRET=<vercel cron secret> | ||
CALENDAR_API_KEY=<google calendar api key> | ||
NTHU_HEADLESS_AIS_SIGNING_KEY=<generate a 32-byte key> | ||
GITHUB_APP_PRIVATE_KEY=YOUR_GITHUB_APP_PRIVATE_KEY | ||
NTHU_HEADLESS_AIS_ENCRYPTION_KEY=<generate a 32-byte key> | ||
CLOUDFLARE_KV_API_TOKEN=YOUR_CLOUDFLARE_KV_API_TOKEN |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { getShortLink } from "@/lib/cloudflarekv"; | ||
import { NextRequest, NextResponse } from "next/server"; | ||
|
||
export const GET = async (req: NextRequest, props: { params: { slug: string }}) => { | ||
const slug = props.params.slug; | ||
if (!slug) { | ||
return NextResponse.json({ error: { message: 'Invalid Slug'} }, { status: 404 }); | ||
} | ||
const url = await getShortLink(slug); | ||
if (url === null) { | ||
return NextResponse.json({ error: { message: 'Link does not exist'} }, { status: 404 }); | ||
} | ||
return NextResponse.redirect(url); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,113 @@ | ||
import { | ||
Button, | ||
DialogContent, | ||
DialogTitle, | ||
IconButton, | ||
Input, | ||
ModalClose, | ||
ModalDialog, | ||
} from '@mui/joy'; | ||
import { Calendar, Mail, Share } from 'lucide-react'; | ||
import { Calendar, Copy, Mail, Share } from 'lucide-react'; | ||
import { QRCodeSVG } from 'qrcode.react'; | ||
import useDictionary from '@/dictionaries/useDictionary'; | ||
import { useEffect, useState } from 'react'; | ||
import { addShortLink } from '@/lib/cloudflarekv'; | ||
import { toast } from '../ui/use-toast'; | ||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog'; | ||
import { Button } from '../ui/button'; | ||
import { Input } from '../ui/input'; | ||
import Link from 'next/link'; | ||
import { Skeleton } from '../ui/skeleton'; | ||
|
||
const ShareSyncTimetableDialog = ({ onClose, shareLink, webcalLink, onCopy }: { onClose: () => void, shareLink: string, webcalLink: string, onCopy: () => void }) => { | ||
|
||
const dict = useDictionary(); | ||
|
||
return <ModalDialog> | ||
<ModalClose /> | ||
<DialogTitle>{dict.dialogs.ShareSyncTimetableDialog.title}</DialogTitle> | ||
<DialogContent> | ||
<p> | ||
{dict.dialogs.ShareSyncTimetableDialog.description} | ||
</p> | ||
<Input | ||
size="lg" | ||
value={shareLink} | ||
endDecorator={ | ||
<IconButton variant="solid" onClick={onCopy}> | ||
<Share className="w-5 h-5" /> | ||
</IconButton> | ||
} /> | ||
const ComponentSkeleton = () => { | ||
return ( | ||
<div className='flex flex-col gap-4'> | ||
<div className='flex flex-row gap-4'> | ||
<Skeleton className="flex-1 p-2 bg-gray-100 dark:bg-neutral-800 rounded-md w-[300px] h-[40px]" /> | ||
<Skeleton className="w-[40px] h-[40px] rounded-full" /> | ||
</div> | ||
<div className="grid grid-cols-2 gap-4"> | ||
<div className="flex flex-col"> | ||
<h3 className="text-lg font-semibold">{dict.dialogs.ShareSyncTimetableDialog['category:qr']}</h3> | ||
<div className='p-2 bg-white rounded-md w-min'> | ||
<QRCodeSVG value={shareLink} /> | ||
</div> | ||
<Skeleton className="text-lg font-semibold w-[150px] h-[24px]" /> | ||
<Skeleton className='p-2 bg-white rounded-md w-[100px] h-[100px]' /> | ||
</div> | ||
<div className="flex flex-col space-y-2"> | ||
<h3 className="text-lg font-semibold">{dict.dialogs.ShareSyncTimetableDialog['category:links']}</h3> | ||
<Button | ||
component="a" | ||
// Subject: Here is My Timetable, Body: My Timetable can be found on NTHUMODS at {shareLink} | ||
href={`mailto:?subject=Here is My Timetable&body=My Timetable can be found on NTHUMODS at ${shareLink}`} | ||
target='_blank' | ||
variant="outlined" | ||
startDecorator={<Mail className="w-4 h-4" />} | ||
>{dict.dialogs.ShareSyncTimetableDialog.links.email}</Button> | ||
<Button | ||
disabled={true} | ||
target='_blank' | ||
variant="outlined" | ||
startDecorator={<Calendar className="w-4 h-4" />} | ||
>Sync To Calendar</Button> | ||
<Skeleton className="text-lg font-semibold w-[150px] h-[24px]" /> | ||
<Button variant="outline" disabled> | ||
<Skeleton className="w-[200px] h-[40px]" /> | ||
</Button> | ||
<Button variant="outline" disabled> | ||
<Skeleton className="w-[200px] h-[40px]" /> | ||
</Button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
const ShareSyncTimetableDialog = ({ shareLink, webcalLink }: { shareLink: string, webcalLink: string }) => { | ||
const [open, setOpen] = useState(false); | ||
const dict = useDictionary(); | ||
const [link, setLink] = useState<string | null>(null); | ||
|
||
useEffect(() => { | ||
if(open) { | ||
addShortLink(shareLink).then((shortLink) => { | ||
if(typeof shortLink == 'object' && 'error' in shortLink) { | ||
toast({ | ||
title: 'Short Link Error', | ||
description: 'Failed to generate short link. Please try again later.', | ||
}) | ||
} | ||
setLink(shortLink as string); | ||
}); | ||
} | ||
}, [open]); | ||
|
||
const handleCopy = () => { | ||
if(link) navigator.clipboard.writeText(link).then(() => { | ||
toast({ | ||
title: 'Copied', | ||
description: 'Link copied to clipboard', | ||
}) | ||
}); | ||
} | ||
return <Dialog open={open} onOpenChange={v => setOpen(v)}> | ||
<DialogTrigger asChild> | ||
<Button variant="outline"><Share className="w-4 h-4 mr-1" /> {dict.timetable.actions.share}</Button> | ||
</DialogTrigger> | ||
<DialogContent> | ||
<DialogHeader> | ||
<DialogTitle>{dict.dialogs.ShareSyncTimetableDialog.title}</DialogTitle> | ||
<DialogDescription>{dict.dialogs.ShareSyncTimetableDialog.description}</DialogDescription> | ||
</DialogHeader> | ||
{link ? <div className='flex flex-col gap-4'> | ||
<div className='flex flex-row gap-4'> | ||
<input type="text" value={link} readOnly className='flex-1 p-2 bg-gray-100 dark:bg-neutral-800 rounded-md' /> | ||
<Button onClick={handleCopy}><Copy className='w-4 h-4'/></Button> | ||
</div><div className="grid grid-cols-2 gap-4"> | ||
<div className="flex flex-col"> | ||
<h3 className="text-lg font-semibold">{dict.dialogs.ShareSyncTimetableDialog['category:qr']}</h3> | ||
<div className='p-2 bg-white rounded-md w-min'> | ||
<QRCodeSVG value={link} /> | ||
</div> | ||
</div> | ||
<div className="flex flex-col space-y-2"> | ||
<h3 className="text-lg font-semibold">{dict.dialogs.ShareSyncTimetableDialog['category:links']}</h3> | ||
<Button variant="outline" asChild> | ||
<Link | ||
// Subject: Here is My Timetable, Body: My Timetable can be found on NTHUMODS at {shareLink} | ||
href={`mailto:?subject=Here is My Timetable&body=My Timetable can be found on NTHUMODS at ${link}`} | ||
target='_blank' | ||
> | ||
<Mail className="w-4 h-4 mr-2" />{dict.dialogs.ShareSyncTimetableDialog.links.email} | ||
</Link> | ||
</Button> | ||
<Button variant="outline" asChild disabled> | ||
<Link | ||
href={webcalLink} | ||
target='_blank' | ||
> | ||
<Calendar className="w-4 h-4 mr-2" /> Sync To Calendar | ||
</Link> | ||
</Button> | ||
</div> | ||
</div> | ||
</div>: | ||
<ComponentSkeleton />} | ||
</DialogContent> | ||
</ModalDialog> | ||
</Dialog> | ||
} | ||
|
||
export default ShareSyncTimetableDialog; |
Oops, something went wrong.