Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: regex custom domain and input with addons (#228)

authored by

Maximilian Kaske and committed by
GitHub
98ad2364 7d0a5b0c

+57 -5
+6 -2
apps/web/src/components/forms/custom-domain-form.tsx
··· 18 18 FormLabel, 19 19 FormMessage, 20 20 } from "@/components/ui/form"; 21 - import { Input } from "@/components/ui/input"; 22 21 import { useDomainStatus } from "@/hooks/use-domain-status"; 23 22 import { api } from "@/trpc/client"; 24 23 import DomainConfiguration from "../domains/domain-configuration"; 25 24 import DomainStatusIcon from "../domains/domain-status-icon"; 26 25 import { LoadingAnimation } from "../loading-animation"; 26 + import { InputWithAddons } from "../ui/input-with-addons"; 27 27 28 28 const customDomain = insertPageSchemaWithMonitors.pick({ 29 29 customDomain: true, ··· 89 89 <FormLabel>Custom Domain</FormLabel> 90 90 <FormControl> 91 91 <div className="flex items-center space-x-3"> 92 - <Input placeholder="acme.com" {...field} /> 92 + <InputWithAddons 93 + placeholder="acme.com" 94 + leading="https://" 95 + {...field} 96 + /> 93 97 <div className="h-full w-7"> 94 98 {/* TODO: add loading state */} 95 99 {status ? <DomainStatusIcon status={status} /> : null}
+2 -1
apps/web/src/components/forms/status-page-form.tsx
··· 24 24 FormMessage, 25 25 } from "@/components/ui/form"; 26 26 import { Input } from "@/components/ui/input"; 27 + import { InputWithAddons } from "@/components/ui/input-with-addons"; 27 28 import { useToast } from "@/components/ui/use-toast"; 28 29 import { useDebounce } from "@/hooks/use-debounce"; 29 30 import { slugify } from "@/lib/utils"; ··· 195 196 <FormItem className="sm:col-span-3"> 196 197 <FormLabel>Slug</FormLabel> 197 198 <FormControl> 198 - <Input placeholder="" {...field} /> 199 + <InputWithAddons {...field} trailing={".openstatus.dev"} /> 199 200 </FormControl> 200 201 <FormDescription> 201 202 The subdomain for your status page. At least 3 chars.
+40
apps/web/src/components/ui/input-with-addons.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + export interface InputWithAddonsProps 6 + extends React.InputHTMLAttributes<HTMLInputElement> { 7 + leading?: React.ReactNode; 8 + trailing?: React.ReactNode; 9 + } 10 + 11 + const InputWithAddons = React.forwardRef< 12 + HTMLInputElement, 13 + InputWithAddonsProps 14 + >(({ leading, trailing, className, ...props }, ref) => { 15 + return ( 16 + <div className="border-input ring-offset-background focus-within:ring-ring group flex h-10 w-full rounded-md border bg-transparent text-sm focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2"> 17 + {leading ? ( 18 + <div className="border-input bg-muted border-r px-3 py-2"> 19 + {leading} 20 + </div> 21 + ) : null} 22 + <input 23 + className={cn( 24 + "placeholder:text-muted-foreground w-full rounded-md px-3 py-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50", 25 + className, 26 + )} 27 + ref={ref} 28 + {...props} 29 + /> 30 + {trailing ? ( 31 + <div className="border-input bg-muted border-l px-3 py-2"> 32 + {trailing} 33 + </div> 34 + ) : null} 35 + </div> 36 + ); 37 + }); 38 + InputWithAddons.displayName = "InputWithAddons"; 39 + 40 + export { InputWithAddons };
+9 -2
packages/db/src/schema/page.ts
··· 50 50 .min(3) 51 51 .toLowerCase(); 52 52 53 + const customDomainSchema = z 54 + .string() 55 + .regex( 56 + new RegExp("^(?!https?://|www.)([a-zA-Z0-9]+(.[a-zA-Z0-9]+)+.*)$"), 57 + "Should not start with http://, https:// or www.", 58 + ); 59 + 53 60 // Schema for inserting a Page - can be used to validate API requests 54 61 export const insertPageSchema = createInsertSchema(page, { 55 - customDomain: z.string().optional(), 62 + customDomain: customDomainSchema.optional(), 56 63 icon: z.string().optional(), 57 64 slug: slugSchema, 58 65 }); 59 66 60 67 export const insertPageSchemaWithMonitors = insertPageSchema.extend({ 61 - customDomain: z.string().optional().default(""), 68 + customDomain: customDomainSchema.optional().default(""), 62 69 monitors: z.array(z.number()).optional(), 63 70 workspaceSlug: z.string().optional(), 64 71 slug: slugSchema,