Add unsend campaign feature (#45)

* Add unsend email editor

Add email editor

Add more email editor

Add renderer partial

Add more marketing email features

* Add more campaign feature

* Add variables

* Getting there

* campaign is there mfs

* Add migration
This commit is contained in:
KM Koushik
2024-08-10 10:09:10 +10:00
committed by GitHub
parent 0c072579b9
commit 5ddc0a7bb9
92 changed files with 11766 additions and 338 deletions

View File

@@ -0,0 +1,65 @@
import { Button } from "@unsend/ui/src/button";
import { CheckIcon } from "lucide-react";
import { useState, useCallback, useMemo } from "react";
export type LinkEditorPanelProps = {
initialUrl?: string;
onSetLink: (url: string) => void;
};
export const useLinkEditorState = ({
initialUrl,
onSetLink,
}: LinkEditorPanelProps) => {
const [url, setUrl] = useState(initialUrl || "");
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setUrl(event.target.value);
}, []);
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
onSetLink(url);
},
[url, onSetLink]
);
return {
url,
setUrl,
onChange,
handleSubmit,
};
};
export const LinkEditorPanel = ({
onSetLink,
initialUrl,
}: LinkEditorPanelProps) => {
const state = useLinkEditorState({
onSetLink,
initialUrl,
});
return (
<div className="">
<form
onSubmit={state.handleSubmit}
className="flex items-center gap-2 justify-between"
>
<label className="flex items-center gap-2 p-2 rounded-lg cursor-text">
<input
className="flex-1 bg-transparent outline-none min-w-[12rem] text-black text-sm"
placeholder="Enter valid url"
value={state.url}
onChange={state.onChange}
/>
</label>
<Button variant="silent" size="sm" className="px-1">
<CheckIcon className="h-4 w-4 disabled:opacity-50" />
</Button>
</form>
</div>
);
};

View File

@@ -0,0 +1,33 @@
import { Button } from "@unsend/ui/src/button";
import { Edit2Icon, EditIcon, Trash2Icon } from "lucide-react";
export type LinkPreviewPanelProps = {
url: string;
onEdit: () => void;
onClear: () => void;
};
export const LinkPreviewPanel = ({
onClear,
onEdit,
url,
}: LinkPreviewPanelProps) => {
return (
<div className="flex items-center gap-2 p-2">
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="text-sm underline w-[12rem] overflow-hidden text-ellipsis"
>
{url}
</a>
<Button onClick={onEdit} variant="silent" size="sm" className="p-1">
<Edit2Icon className="h-4 w-4" />
</Button>
<Button onClick={onClear} variant="silent" size="sm" className="p-1">
<Trash2Icon className="h-4 w-4 text-destructive" />
</Button>
</div>
);
};

View File

@@ -0,0 +1,37 @@
"use client";
import { useState } from "react";
import { HexAlphaColorPicker, HexColorInput } from "react-colorful";
type ColorPickerProps = {
color: string;
onChange?: (color: string) => void;
};
export function ColorPicker(props: ColorPickerProps) {
const { color: initialColor, onChange } = props;
const [color, setColor] = useState(initialColor);
const handleColorChange = (color: string) => {
setColor(color);
onChange?.(color);
};
return (
<div className="min-w-[260px] rounded-xl border bg-white p-4">
<HexAlphaColorPicker
color={color}
onChange={handleColorChange}
className="flex !w-full flex-col gap-4"
/>
<HexColorInput
alpha={true}
color={color}
onChange={handleColorChange}
className="mt-4 bg-transparent text-black w-full min-w-0 rounded-lg border px-2 py-1.5 text-sm uppercase focus-visible:border-gray-400 focus-visible:outline-none"
prefixed
/>
</div>
);
}

View File

@@ -0,0 +1,18 @@
import React from "react";
import { SVGProps } from "../../../types";
export const BorderWidth: React.FC<SVGProps> = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
{...props}
>
<path d="M0 3.5A.5.5 0 0 1 .5 3h15a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H.5a.5.5 0 0 1-.5-.5zm0 5A.5.5 0 0 1 .5 8h15a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5H.5a.5.5 0 0 1-.5-.5zm0 4a.5.5 0 0 1 .5-.5h15a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5" />
</svg>
);
};