Openstatus www.openstatus.dev

Feat/small stuff and upload dimension (#705)

* feat: add upload validation

* chore: small stuff

authored by

Maximilian Kaske and committed by
GitHub
61a5610a ddeee5a4

+72 -8
+4 -4
apps/web/src/app/(content)/_components/pagination.tsx
··· 17 17 {/* {prev ? ( 18 18 <div className="w-1/2 flex-1 text-left"> 19 19 <Button asChild variant="link"> 20 - <Link href={`/changelog/${prev.slug}`}> 21 - <ChevronLeft className="mr-2 h-4 w-4" /> 20 + <Link href={`/changelog/${prev.slug}`} className="group"> 21 + <ChevronLeft className="text-muted-foreground group-hover:text-foreground mr-2 h-4 w-4" /> 22 22 <span className="truncate">{prev.title}</span> 23 23 </Link> 24 24 </Button> ··· 29 29 {next ? ( 30 30 <div className="w-1/2 flex-1 text-right"> 31 31 <Button asChild variant="link"> 32 - <Link href={`/changelog/${next.slug}`}> 32 + <Link href={`/changelog/${next.slug}`} className="group"> 33 33 <span className="truncate">{next.title}</span> 34 - <ChevronRight className="ml-2 h-4 w-4" /> 34 + <ChevronRight className="text-muted-foreground group-hover:text-foreground ml-2 h-4 w-4" /> 35 35 </Link> 36 36 </Button> 37 37 </div>
+67 -3
apps/web/src/components/forms/status-page/section-advanced.tsx
··· 8 8 9 9 import type { InsertPage } from "@openstatus/db/src/schema"; 10 10 import { 11 + AlertDialog, 12 + AlertDialogAction, 13 + AlertDialogCancel, 14 + AlertDialogContent, 15 + AlertDialogDescription, 16 + AlertDialogFooter, 17 + AlertDialogHeader, 18 + AlertDialogTitle, 11 19 Button, 12 20 FormControl, 13 21 FormDescription, ··· 26 34 27 35 export function SectionAdvanced({ form }: Props) { 28 36 const inputFileRef = useRef<HTMLInputElement>(null); 37 + const [open, setOpen] = React.useState(false); 38 + const [file, setFile] = React.useState<File | null>(null); 39 + 40 + /** 41 + * Determine the width and height of the uploaded image - it ideally is a square 42 + */ 43 + const getFileDimensions = async (file: File) => { 44 + const img = document.createElement("img"); 45 + img.src = URL.createObjectURL(file); 46 + await img.decode(); 47 + return { width: img.naturalWidth, height: img.naturalHeight }; 48 + }; 29 49 30 50 const handleChange = async (file: FileList | null) => { 31 51 if (!file || file.length === 0) { 32 52 return; 33 53 } 34 54 35 - const response = await fetch(`/api/upload?filename=${file[0].name}`, { 55 + const { height, width } = await getFileDimensions(file[0]); 56 + 57 + // remove rounding issues from transformations 58 + if (!(Math.abs(height - width) <= 1)) { 59 + setOpen(true); 60 + setFile(file[0]); 61 + return; 62 + } 63 + 64 + const newblob = await handleUpload(file[0]); 65 + form.setValue("icon", newblob.url); 66 + }; 67 + 68 + const handleUpload = async (file: File) => { 69 + const response = await fetch(`/api/upload?filename=${file.name}`, { 36 70 method: "POST", 37 - body: file[0], 71 + body: file, 38 72 }); 39 73 40 74 const newblob = (await response.json()) as PutBlobResult; 41 - form.setValue("icon", newblob.url); 75 + return newblob; 76 + }; 77 + 78 + const handleCancel = () => { 79 + inputFileRef.current?.value && (inputFileRef.current.value = ""); 80 + }; 81 + 82 + const handleConfirm = async () => { 83 + if (file) { 84 + const newblob = await handleUpload(file); 85 + form.setValue("icon", newblob.url); 86 + setFile(null); 87 + } 88 + setOpen(false); 42 89 }; 43 90 44 91 return ( ··· 110 157 </FormItem> 111 158 )} 112 159 /> 160 + <AlertDialog open={open} onOpenChange={(value) => setOpen(value)}> 161 + <AlertDialogContent> 162 + <AlertDialogHeader> 163 + <AlertDialogTitle>Incorrect image size</AlertDialogTitle> 164 + <AlertDialogDescription> 165 + For the best result, the image should be a square. You can still 166 + upload it, but it will be cropped. 167 + </AlertDialogDescription> 168 + </AlertDialogHeader> 169 + <AlertDialogFooter> 170 + <AlertDialogCancel onClick={handleCancel}>Cancel</AlertDialogCancel> 171 + <AlertDialogAction onClick={handleConfirm}> 172 + Continue 173 + </AlertDialogAction> 174 + </AlertDialogFooter> 175 + </AlertDialogContent> 176 + </AlertDialog> 113 177 </div> 114 178 ); 115 179 }
+1 -1
apps/web/src/components/marketing/hero.tsx
··· 32 32 > 33 33 A better way to monitor your services. 34 34 </h1> 35 - <p className="text-muted-foreground mx-auto max-w-md text-lg md:max-w-lg md:text-xl"> 35 + <p className="text-muted-foreground mx-auto max-w-md text-lg md:max-w-xl md:text-xl"> 36 36 Monitor your API and website from 6 continents, detect some 37 37 performance issues and receive notifications before your users are 38 38 affected.