-
Notifications
You must be signed in to change notification settings - Fork 793
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add watermarking * fix: proper export * fix * fix: imports * fix: duplicate * fix: asset prefix * feat: limit to trial and datarooms plan * feat: improve watermarking * feat: add ip address and cleanup types
- Loading branch information
Showing
28 changed files
with
1,182 additions
and
20 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
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,309 @@ | ||
import React, { useEffect, useState } from "react"; | ||
|
||
import { motion } from "framer-motion"; | ||
import { HexColorInput, HexColorPicker } from "react-colorful"; | ||
import { z } from "zod"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { Checkbox } from "@/components/ui/checkbox"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { | ||
Popover, | ||
PopoverContent, | ||
PopoverTrigger, | ||
} from "@/components/ui/popover"; | ||
import { | ||
Select, | ||
SelectContent, | ||
SelectItem, | ||
SelectTrigger, | ||
SelectValue, | ||
} from "@/components/ui/select"; | ||
import { | ||
Sheet, | ||
SheetContent, | ||
SheetDescription, | ||
SheetFooter, | ||
SheetHeader, | ||
SheetTitle, | ||
} from "@/components/ui/sheet"; | ||
|
||
import { FADE_IN_ANIMATION_SETTINGS } from "@/lib/constants"; | ||
import { WatermarkConfig, WatermarkConfigSchema } from "@/lib/types"; | ||
|
||
interface WatermarkConfigSheetProps { | ||
isOpen: boolean; | ||
onOpenChange: (open: boolean) => void; | ||
initialConfig: Partial<WatermarkConfig>; | ||
onSave: (config: WatermarkConfig) => void; | ||
} | ||
|
||
export default function WatermarkConfigSheet({ | ||
isOpen, | ||
onOpenChange, | ||
initialConfig, | ||
onSave, | ||
}: WatermarkConfigSheetProps) { | ||
const [formValues, setFormValues] = | ||
useState<Partial<WatermarkConfig>>(initialConfig); | ||
const [errors, setErrors] = useState<Record<string, string>>({}); | ||
|
||
useEffect(() => { | ||
setFormValues(initialConfig); | ||
}, [initialConfig]); | ||
|
||
const handleInputChange = ( | ||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>, | ||
) => { | ||
const { name, value } = e.target; | ||
setFormValues((prevValues) => ({ | ||
...prevValues, | ||
[name]: value, | ||
})); | ||
}; | ||
|
||
const validateAndSave = () => { | ||
try { | ||
const validatedData = WatermarkConfigSchema.parse(formValues); | ||
onSave(validatedData); | ||
setErrors({}); | ||
onOpenChange(false); | ||
} catch (error) { | ||
if (error instanceof z.ZodError) { | ||
const fieldErrors: Record<string, string> = {}; | ||
error.errors.forEach((err) => { | ||
if (err.path[0]) { | ||
fieldErrors[err.path[0] as string] = err.message; | ||
} | ||
}); | ||
setErrors(fieldErrors); | ||
} | ||
} | ||
}; | ||
|
||
return ( | ||
<Sheet open={isOpen} onOpenChange={onOpenChange}> | ||
<SheetContent> | ||
<SheetHeader> | ||
<SheetTitle>Watermark Configuration</SheetTitle> | ||
<SheetDescription> | ||
Configure the watermark settings for your document. | ||
</SheetDescription> | ||
</SheetHeader> | ||
|
||
<motion.div | ||
className="relative mt-4 space-y-3" | ||
{...FADE_IN_ANIMATION_SETTINGS} | ||
> | ||
<div className="flex w-full flex-col items-start gap-6 overflow-x-visible pb-4 pt-0"> | ||
<div className="w-full space-y-2"> | ||
<Label htmlFor="watermark-text">Watermark Text</Label> | ||
<Input | ||
id="watermark-text" | ||
type="text" | ||
name="text" | ||
placeholder="e.g. Confidential {{email}} {{date}}" | ||
value={formValues.text || ""} | ||
onChange={handleInputChange} | ||
className="focus:ring-inset" | ||
/> | ||
<div className="space-x-1"> | ||
{["email", "date", "time", "link", "ipAddress"].map((item) => ( | ||
<Button | ||
key={item} | ||
size="sm" | ||
variant="outline" | ||
className="h-7 rounded-3xl bg-muted text-sm font-normal text-foreground/80 hover:bg-muted/70" | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
setFormValues((prevValues) => ({ | ||
...prevValues, | ||
text: `${prevValues.text || ""}{{${item}}}`, | ||
})); | ||
}} | ||
>{`{{${item}}}`}</Button> | ||
))} | ||
</div> | ||
{errors.text && <p className="text-red-500">{errors.text}</p>} | ||
</div> | ||
|
||
<div className="w-full space-y-2"> | ||
<div className="relative flex items-center space-x-2"> | ||
<Checkbox | ||
id="watermark-tiled" | ||
checked={formValues.isTiled} | ||
onCheckedChange={(checked) => { | ||
setFormValues((prevValues) => ({ | ||
...prevValues, | ||
isTiled: checked === true, | ||
})); | ||
}} | ||
className="mt-0.5 border border-gray-400 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-300 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-white data-[state=checked]:bg-black data-[state=checked]:text-white" | ||
/> | ||
<Label htmlFor="watermark-tiled">Tiled</Label> | ||
</div> | ||
{errors.isTiled && ( | ||
<p className="text-red-500">{errors.isTiled}</p> | ||
)} | ||
</div> | ||
|
||
<div className="w-full space-y-2"> | ||
<Label htmlFor="watermark-position">Position</Label> | ||
<Select | ||
name="position" | ||
value={formValues.position} | ||
disabled={formValues.isTiled} | ||
onValueChange={(value) => { | ||
setFormValues({ | ||
...formValues, | ||
position: value as WatermarkConfig["position"], | ||
}); | ||
}} | ||
> | ||
<SelectTrigger> | ||
<SelectValue placeholder="Select position" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
<SelectItem value="top-left">Top Left</SelectItem> | ||
<SelectItem value="top-center">Top Center</SelectItem> | ||
<SelectItem value="top-right">Top Right</SelectItem> | ||
<SelectItem value="middle-left">Middle Left</SelectItem> | ||
<SelectItem value="middle-center">Middle Center</SelectItem> | ||
<SelectItem value="middle-right">Middle Right</SelectItem> | ||
<SelectItem value="bottom-left">Bottom Left</SelectItem> | ||
<SelectItem value="bottom-center">Bottom Center</SelectItem> | ||
<SelectItem value="bottom-right">Bottom Right</SelectItem> | ||
</SelectContent> | ||
</Select> | ||
{errors.position && ( | ||
<p className="text-red-500">{errors.position}</p> | ||
)} | ||
</div> | ||
|
||
<div className="w-full space-y-2"> | ||
<Label htmlFor="watermark-rotation">Rotation</Label> | ||
<Select | ||
name="rotation" | ||
value={formValues.rotation?.toString()} | ||
onValueChange={(value) => { | ||
setFormValues({ | ||
...formValues, | ||
rotation: parseInt(value) as WatermarkConfig["rotation"], | ||
}); | ||
}} | ||
> | ||
<SelectTrigger> | ||
<SelectValue placeholder="Select rotation" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
<SelectItem value="0">0°</SelectItem> | ||
<SelectItem value="30">30°</SelectItem> | ||
<SelectItem value="45">45°</SelectItem> | ||
<SelectItem value="90">90°</SelectItem> | ||
<SelectItem value="180">180°</SelectItem> | ||
</SelectContent> | ||
</Select> | ||
{errors.rotation && ( | ||
<p className="text-red-500">{errors.rotation}</p> | ||
)} | ||
</div> | ||
|
||
<div className="w-full space-y-2"> | ||
<Label htmlFor="watermark-color">Text Color</Label> | ||
<div className="ml-0.5 mr-0.5 flex space-x-1"> | ||
<Popover> | ||
<PopoverTrigger> | ||
<div | ||
className="h-9 w-9 cursor-pointer rounded-md shadow-sm ring-1 ring-muted-foreground hover:ring-1 hover:ring-gray-300 focus:ring-inset" | ||
style={{ backgroundColor: formValues.color }} | ||
/> | ||
</PopoverTrigger> | ||
<PopoverContent> | ||
<HexColorPicker | ||
color={formValues.color || ""} | ||
onChange={(value) => { | ||
setFormValues({ | ||
...formValues, | ||
color: value as WatermarkConfig["color"], | ||
}); | ||
}} | ||
/> | ||
</PopoverContent> | ||
</Popover> | ||
<HexColorInput | ||
className="flex w-full rounded-md border border-input bg-white text-foreground placeholder-muted-foreground focus:border-muted-foreground focus:outline-none focus:ring-inset focus:ring-muted-foreground dark:border-gray-500 dark:bg-gray-800 focus:dark:bg-transparent sm:text-sm" | ||
color={formValues.color || ""} | ||
onChange={(value) => { | ||
setFormValues({ | ||
...formValues, | ||
color: value as WatermarkConfig["color"], | ||
}); | ||
}} | ||
prefixed | ||
/> | ||
</div> | ||
{errors.color && <p className="text-red-500">{errors.color}</p>} | ||
</div> | ||
|
||
<div className="flex w-full space-x-4"> | ||
<div className="w-full space-y-2"> | ||
<Label htmlFor="watermark-fontSize">Font Size</Label> | ||
<Input | ||
id="watermark-fontSize" | ||
type="number" | ||
name="fontSize" | ||
step="4" | ||
value={formValues.fontSize} | ||
onChange={(e) => { | ||
setFormValues({ | ||
...formValues, | ||
fontSize: parseInt( | ||
e.target.value, | ||
) as WatermarkConfig["fontSize"], | ||
}); | ||
}} | ||
className="focus:ring-inset" | ||
/> | ||
{errors.fontSize && ( | ||
<p className="text-red-500">{errors.fontSize}</p> | ||
)} | ||
</div> | ||
<div className="w-full space-y-2"> | ||
<Label htmlFor="watermark-opacity">Transparency</Label> | ||
<Select | ||
name="opacity" | ||
value={formValues.opacity?.toString()} | ||
onValueChange={(value) => { | ||
setFormValues({ | ||
...formValues, | ||
opacity: parseFloat(value) as WatermarkConfig["opacity"], | ||
}); | ||
}} | ||
> | ||
<SelectTrigger> | ||
<SelectValue placeholder="Select transparency" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
<SelectItem value="1">No transparency</SelectItem> | ||
<SelectItem value="0.25">75%</SelectItem> | ||
<SelectItem value="0.5">50%</SelectItem> | ||
<SelectItem value="0.75">25%</SelectItem> | ||
</SelectContent> | ||
</Select> | ||
{errors.opacity && ( | ||
<p className="text-red-500">{errors.opacity}</p> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</motion.div> | ||
|
||
<SheetFooter> | ||
<Button onClick={validateAndSave}>Save Watermark</Button> | ||
</SheetFooter> | ||
</SheetContent> | ||
</Sheet> | ||
); | ||
} |
Oops, something went wrong.