mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-07 21:29:13 +00:00
Update automations UX for more consistency (#856)
* Update the automations UI to be a more suitable color distribution based on new designs * Use accented colors for the metadata, update dark mode colors * Update form to use icons as well and render more pretty inline form labels
This commit is contained in:
@@ -4,6 +4,11 @@ div.automationsLayout {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.automationCard {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
div.automationsLayout {
|
div.automationsLayout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ import styles from './automations.module.css';
|
|||||||
import ShareLink from '../components/shareLink/shareLink';
|
import ShareLink from '../components/shareLink/shareLink';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
|
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
|
||||||
import { Clock, DotsThreeVertical, Envelope, Info, MapPinSimple, Pencil, Play, Plus, Trash } from '@phosphor-icons/react';
|
import { CalendarCheck, CalendarDot, CalendarDots, Clock, ClockAfternoon, ClockCounterClockwise, DotsThreeVertical, Envelope, Info, Lightning, MapPinSimple, Pencil, Play, Plus, Trash } from '@phosphor-icons/react';
|
||||||
import { useAuthenticatedData } from '../common/auth';
|
import { useAuthenticatedData, UserProfile } from '../common/auth';
|
||||||
import LoginPrompt from '../components/loginPrompt/loginPrompt';
|
import LoginPrompt from '../components/loginPrompt/loginPrompt';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { ToastAction } from '@/components/ui/toast';
|
import { ToastAction } from '@/components/ui/toast';
|
||||||
@@ -223,6 +223,7 @@ interface AutomationsCardProps {
|
|||||||
setNewAutomationData?: (data: AutomationsData) => void;
|
setNewAutomationData?: (data: AutomationsData) => void;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
||||||
|
authenticatedData: UserProfile | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -235,6 +236,33 @@ function AutomationsCard(props: AutomationsCardProps) {
|
|||||||
|
|
||||||
const automation = props.automation;
|
const automation = props.automation;
|
||||||
|
|
||||||
|
const [timeRecurrence, setTimeRecurrence] = useState('');
|
||||||
|
|
||||||
|
const [intervalString, setIntervalString] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// The updated automation data, if present, takes priority over the original automation data
|
||||||
|
const automationData = updatedAutomationData || automation;
|
||||||
|
setTimeRecurrence(getTimeRecurrenceFromCron(automationData.crontime));
|
||||||
|
const frequency = getEveryBlahFromCron(automationData.crontime);
|
||||||
|
|
||||||
|
console.log('frequency', frequency);
|
||||||
|
if (frequency === 'Day') {
|
||||||
|
setIntervalString('Daily');
|
||||||
|
} else if (frequency === 'Week') {
|
||||||
|
const dayOfWeek = getDayOfWeekFromCron(automationData.crontime);
|
||||||
|
if (dayOfWeek === undefined) {
|
||||||
|
setIntervalString('Weekly');
|
||||||
|
} else {
|
||||||
|
setIntervalString(`${weekDays[dayOfWeek]}`);
|
||||||
|
}
|
||||||
|
} else if (frequency === 'Month') {
|
||||||
|
const dayOfMonth = getDayOfMonthFromCron(automationData.crontime);
|
||||||
|
setIntervalString(`Monthly on the ${dayOfMonth}`);
|
||||||
|
}
|
||||||
|
}, [updatedAutomationData, props.automation]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const toastTitle = `Automation: ${updatedAutomationData?.subject || automation.subject}`;
|
const toastTitle = `Automation: ${updatedAutomationData?.subject || automation.subject}`;
|
||||||
if (toastMessage) {
|
if (toastMessage) {
|
||||||
@@ -254,117 +282,128 @@ function AutomationsCard(props: AutomationsCardProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='p-2 rounded-lg bg-secondary hover:shadow-md'>
|
<Card className={`bg-secondary h-full shadow-sm rounded-lg bg-gradient-to-b from-background to-slate-50 dark:to-gray-950 border ${styles.automationCard}`}>
|
||||||
<Card className='bg-secondary h-full shadow-none border-l-4 border-t-0 border-r-0 border-b-0 border-l-green-400 dark:border-green-600 rounded-none'>
|
<CardHeader>
|
||||||
<CardHeader>
|
<CardTitle className='line-clamp-2 leading-normal flex justify-between'>
|
||||||
<CardTitle className='line-clamp-2 leading-normal flex justify-between'>
|
{updatedAutomationData?.subject || automation.subject}
|
||||||
{updatedAutomationData?.subject || automation.subject}
|
<Popover>
|
||||||
<Popover>
|
<PopoverTrigger asChild>
|
||||||
<PopoverTrigger asChild>
|
<Button className='bg-background' variant={'ghost'}><DotsThreeVertical className='h-4 w-4' /></Button>
|
||||||
<Button className='bg-background' variant={'ghost'}><DotsThreeVertical className='h-4 w-4' /></Button>
|
</PopoverTrigger>
|
||||||
</PopoverTrigger>
|
<PopoverContent className='w-auto grid gap-2 text-left bg-secondary'>
|
||||||
<PopoverContent className='w-auto grid gap-2 text-left'>
|
<Button variant={'destructive'}
|
||||||
<Button variant={'destructive'}
|
className='justify-start'
|
||||||
className='justify-start'
|
onClick={() => {
|
||||||
onClick={() => {
|
if (props.suggestedCard) {
|
||||||
if (props.suggestedCard) {
|
setIsDeleted(true);
|
||||||
setIsDeleted(true);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
deleteAutomation(automation.id.toString(), setIsDeleted);
|
||||||
deleteAutomation(automation.id.toString(), setIsDeleted);
|
}}>
|
||||||
}}>
|
<Trash className='h-4 w-4 mr-2' />Delete
|
||||||
<Trash className='h-4 w-4 mr-2' />Delete
|
</Button>
|
||||||
</Button>
|
{
|
||||||
{
|
!props.suggestedCard && (
|
||||||
!props.suggestedCard && (
|
<Dialog
|
||||||
<Dialog
|
open={isEditing}
|
||||||
open={isEditing}
|
onOpenChange={(open) => {
|
||||||
onOpenChange={(open) => {
|
setIsEditing(open);
|
||||||
setIsEditing(open);
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<DialogTrigger asChild>
|
||||||
<DialogTrigger asChild>
|
<Button variant="outline" className="justify-start">
|
||||||
<Button variant="outline" className="justify-start">
|
<Pencil className='h-4 w-4 mr-2' />Edit
|
||||||
<Pencil className='h-4 w-4 mr-2' />Edit
|
</Button>
|
||||||
</Button>
|
</DialogTrigger>
|
||||||
</DialogTrigger>
|
<DialogContent>
|
||||||
<DialogContent>
|
<DialogTitle>Edit Automation</DialogTitle>
|
||||||
<DialogTitle>Edit Automation</DialogTitle>
|
<EditCard
|
||||||
<EditCard
|
authenticatedData={props.authenticatedData}
|
||||||
automation={automation}
|
automation={automation}
|
||||||
setIsEditing={setIsEditing}
|
setIsEditing={setIsEditing}
|
||||||
isLoggedIn={props.isLoggedIn}
|
isLoggedIn={props.isLoggedIn}
|
||||||
setShowLoginPrompt={props.setShowLoginPrompt}
|
setShowLoginPrompt={props.setShowLoginPrompt}
|
||||||
setUpdatedAutomationData={setUpdatedAutomationData}
|
setUpdatedAutomationData={setUpdatedAutomationData}
|
||||||
locationData={props.locationData} />
|
locationData={props.locationData} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!props.suggestedCard && (
|
!props.suggestedCard && (
|
||||||
<Button variant={'outline'}
|
<Button variant={'outline'}
|
||||||
className="justify-start"
|
className="justify-start"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
sendAPreview(automation.id.toString(), setToastMessage);
|
sendAPreview(automation.id.toString(), setToastMessage);
|
||||||
}}>
|
}}>
|
||||||
<Play className='h-4 w-4 mr-2' />Run Now
|
<Play className='h-4 w-4 mr-2' />Run Now
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription className='mt-2'>
|
|
||||||
{updatedAutomationData?.schedule || cronToHumanReadableString(automation.crontime)}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{updatedAutomationData?.query_to_run || automation.query_to_run}
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex justify-end gap-2">
|
|
||||||
{
|
|
||||||
props.suggestedCard && props.setNewAutomationData && (
|
|
||||||
<Dialog
|
|
||||||
open={isEditing}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
setIsEditing(open);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="outline">
|
|
||||||
<Plus className='h-4 w-4 mr-2' />
|
|
||||||
Add
|
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
)
|
||||||
<DialogContent>
|
}
|
||||||
<DialogTitle>Add Automation</DialogTitle>
|
<ShareLink
|
||||||
<EditCard
|
buttonTitle="Share"
|
||||||
createNew={true}
|
includeIcon={true}
|
||||||
automation={automation}
|
buttonClassName='justify-start px-4 py-2 h-10'
|
||||||
setIsEditing={setIsEditing}
|
buttonVariant={'outline' as keyof typeof buttonVariants}
|
||||||
isLoggedIn={props.isLoggedIn}
|
title="Share Automation"
|
||||||
setShowLoginPrompt={props.setShowLoginPrompt}
|
description="Copy the link below and share it with your coworkers or friends."
|
||||||
setUpdatedAutomationData={props.setNewAutomationData}
|
url={createShareLink(automation)}
|
||||||
locationData={props.locationData} />
|
onShare={() => {
|
||||||
</DialogContent>
|
navigator.clipboard.writeText(createShareLink(automation));
|
||||||
</Dialog>
|
}} />
|
||||||
)
|
</PopoverContent>
|
||||||
}
|
</Popover>
|
||||||
<ShareLink
|
</CardTitle>
|
||||||
buttonTitle="Share"
|
</CardHeader>
|
||||||
includeIcon={true}
|
<CardContent className='text-secondary-foreground'>
|
||||||
buttonVariant={'outline' as keyof typeof buttonVariants}
|
{updatedAutomationData?.query_to_run || automation.query_to_run}
|
||||||
title="Share Automation"
|
</CardContent>
|
||||||
description="Copy the link below and share it with your coworkers or friends."
|
<CardFooter className="flex flex-col items-start md:flex-row md:justify-between md:items-center gap-2">
|
||||||
url={createShareLink(automation)}
|
<div className='flex gap-2'>
|
||||||
onShare={() => {
|
<div className='flex items-center bg-blue-50 rounded-lg p-1.5 border-blue-200 border dark:bg-blue-800 dark:border-blue-500'>
|
||||||
navigator.clipboard.writeText(createShareLink(automation));
|
<CalendarCheck className='h-4 w-4 mr-2 text-blue-700 dark:text-blue-300' />
|
||||||
}} />
|
<div className='text-s text-blue-700 dark:text-blue-300'>
|
||||||
</CardFooter>
|
{timeRecurrence}
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='flex items-center bg-purple-50 rounded-lg p-1.5 border-purple-200 border dark:bg-purple-800 dark:border-purple-500'>
|
||||||
|
<ClockAfternoon className='h-4 w-4 mr-2 text-purple-700 dark:text-purple-300' />
|
||||||
|
<div className='text-s text-purple-700 dark:text-purple-300'>
|
||||||
|
{intervalString}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
props.suggestedCard && props.setNewAutomationData && (
|
||||||
|
<Dialog
|
||||||
|
open={isEditing}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsEditing(open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="outline">
|
||||||
|
<Plus className='h-4 w-4 mr-2' />
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogTitle>Add Automation</DialogTitle>
|
||||||
|
<EditCard
|
||||||
|
authenticatedData={props.authenticatedData}
|
||||||
|
createNew={true}
|
||||||
|
automation={automation}
|
||||||
|
setIsEditing={setIsEditing}
|
||||||
|
isLoggedIn={props.isLoggedIn}
|
||||||
|
setShowLoginPrompt={props.setShowLoginPrompt}
|
||||||
|
setUpdatedAutomationData={props.setNewAutomationData}
|
||||||
|
locationData={props.locationData} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,6 +412,7 @@ interface SharedAutomationCardProps {
|
|||||||
setNewAutomationData: (data: AutomationsData) => void;
|
setNewAutomationData: (data: AutomationsData) => void;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
||||||
|
authenticatedData: UserProfile | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SharedAutomationCard(props: SharedAutomationCardProps) {
|
function SharedAutomationCard(props: SharedAutomationCardProps) {
|
||||||
@@ -407,8 +447,12 @@ function SharedAutomationCard(props: SharedAutomationCardProps) {
|
|||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogTitle>Create Automation</DialogTitle>
|
<DialogTitle>
|
||||||
|
<Plus className='h-4 w-4 mr-2' />
|
||||||
|
Create Automation
|
||||||
|
</DialogTitle>
|
||||||
<EditCard
|
<EditCard
|
||||||
|
authenticatedData={props.authenticatedData}
|
||||||
createNew={true}
|
createNew={true}
|
||||||
setIsEditing={setIsCreating}
|
setIsEditing={setIsCreating}
|
||||||
setUpdatedAutomationData={props.setNewAutomationData}
|
setUpdatedAutomationData={props.setNewAutomationData}
|
||||||
@@ -438,6 +482,7 @@ interface EditCardProps {
|
|||||||
createNew?: boolean;
|
createNew?: boolean;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
||||||
|
authenticatedData: UserProfile | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditCard(props: EditCardProps) {
|
function EditCard(props: EditCardProps) {
|
||||||
@@ -524,7 +569,14 @@ function EditCard(props: EditCardProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutomationModificationForm form={form} onSubmit={onSubmit} create={props.createNew} isLoggedIn={props.isLoggedIn} setShowLoginPrompt={props.setShowLoginPrompt} />
|
<AutomationModificationForm
|
||||||
|
authenticatedData={props.authenticatedData}
|
||||||
|
locationData={props.locationData || null}
|
||||||
|
form={form}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
create={props.createNew}
|
||||||
|
isLoggedIn={props.isLoggedIn}
|
||||||
|
setShowLoginPrompt={props.setShowLoginPrompt} />
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -535,6 +587,8 @@ interface AutomationModificationFormProps {
|
|||||||
create?: boolean;
|
create?: boolean;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
setShowLoginPrompt: (showLoginPrompt: boolean) => void;
|
||||||
|
authenticatedData: UserProfile | null;
|
||||||
|
locationData: LocationData | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AutomationModificationForm(props: AutomationModificationFormProps) {
|
function AutomationModificationForm(props: AutomationModificationFormProps) {
|
||||||
@@ -547,7 +601,7 @@ function AutomationModificationForm(props: AutomationModificationFormProps) {
|
|||||||
function recommendationPill(recommendationText: string, onChange: (value: any, event: React.MouseEvent<HTMLButtonElement>) => void) {
|
function recommendationPill(recommendationText: string, onChange: (value: any, event: React.MouseEvent<HTMLButtonElement>) => void) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className='text-xs bg-slate-50 h-auto p-1.5 m-1 rounded-full'
|
className='text-xs bg-slate-50 dark:bg-slate-950 h-auto p-1.5 m-1 rounded-full'
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
key={recommendationText}
|
key={recommendationText}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
@@ -572,6 +626,16 @@ function AutomationModificationForm(props: AutomationModificationFormProps) {
|
|||||||
props.onSubmit(values);
|
props.onSubmit(values);
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
})} className="space-y-8">
|
})} className="space-y-8">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Setup</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Emails will be sent to this address. Timezone and location data will be used to schedule automations.
|
||||||
|
{
|
||||||
|
props.locationData &&
|
||||||
|
metadataMap(props.locationData, props.authenticatedData)
|
||||||
|
}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
{
|
{
|
||||||
!props.create && (
|
!props.create && (
|
||||||
<FormField
|
<FormField
|
||||||
@@ -600,14 +664,20 @@ function AutomationModificationForm(props: AutomationModificationFormProps) {
|
|||||||
<FormItem
|
<FormItem
|
||||||
className='w-full'
|
className='w-full'
|
||||||
>
|
>
|
||||||
<FormLabel>Frequency</FormLabel>
|
<FormLabel>
|
||||||
|
Frequency
|
||||||
|
</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
How frequently should this automation run?
|
How often should this automation run?
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger className='w-[200px]'>
|
<SelectTrigger className='w-[200px]'>
|
||||||
Every <SelectValue placeholder="" />
|
<div className='flex items-center'>
|
||||||
|
<CalendarDots className='h-4 w-4 mr-2 inline' />
|
||||||
|
Every
|
||||||
|
</div>
|
||||||
|
<SelectValue placeholder="" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -631,11 +701,17 @@ function AutomationModificationForm(props: AutomationModificationFormProps) {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem
|
<FormItem
|
||||||
className='w-full'>
|
className='w-full'>
|
||||||
<FormLabel>Day of Week</FormLabel>
|
<FormDescription>
|
||||||
|
Every week, on which day should this automation run?
|
||||||
|
</FormDescription>
|
||||||
<Select onValueChange={(value) => field.onChange(Number(value))} defaultValue={String(field.value)}>
|
<Select onValueChange={(value) => field.onChange(Number(value))} defaultValue={String(field.value)}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger className='w-[200px]'>
|
<SelectTrigger className='w-[200px]'>
|
||||||
On <SelectValue placeholder="" />
|
<div className='flex items-center'>
|
||||||
|
<CalendarDot className='h-4 w-4 mr-2 inline' />
|
||||||
|
On
|
||||||
|
</div>
|
||||||
|
<SelectValue placeholder="" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -663,11 +739,15 @@ function AutomationModificationForm(props: AutomationModificationFormProps) {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem
|
<FormItem
|
||||||
className='w-full'>
|
className='w-full'>
|
||||||
<FormLabel>Day of Month</FormLabel>
|
<FormDescription>Every month, on which day should the automation run?</FormDescription>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger className='w-[200px]'>
|
<SelectTrigger className='w-[200px]'>
|
||||||
On the <SelectValue placeholder="" />
|
<div className='flex items-center'>
|
||||||
|
<CalendarDot className='h-4 w-4 mr-2 inline' />
|
||||||
|
On the
|
||||||
|
</div>
|
||||||
|
<SelectValue placeholder="" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -705,7 +785,11 @@ function AutomationModificationForm(props: AutomationModificationFormProps) {
|
|||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger className='w-[200px]'>
|
<SelectTrigger className='w-[200px]'>
|
||||||
At <SelectValue placeholder="" />
|
<div className='flex items-center'>
|
||||||
|
<ClockAfternoon className='h-4 w-4 mr-2 inline' />
|
||||||
|
At
|
||||||
|
</div>
|
||||||
|
<SelectValue placeholder="" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -781,6 +865,30 @@ function AutomationModificationForm(props: AutomationModificationFormProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function metadataMap(ipLocationData: LocationData, authenticatedData: UserProfile | null) {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-wrap gap-2 items-center md:justify-start justify-end'>
|
||||||
|
{
|
||||||
|
authenticatedData ? (
|
||||||
|
<span className='rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm' ><Envelope className='h-4 w-4 mr-2 inline text-orange-500' shadow-s />{authenticatedData.email}</span>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ipLocationData && (
|
||||||
|
<span className='rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm' ><MapPinSimple className='h-4 w-4 mr-2 inline text-purple-500' />{ipLocationData ? `${ipLocationData.city}, ${ipLocationData.country}` : 'Unknown'}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ipLocationData && (
|
||||||
|
<span className='rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm' ><Clock className='h-4 w-4 mr-2 inline text-green-500' />{ipLocationData ? `${ipLocationData.timezone}` : 'Unknown'}</span>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function Automations() {
|
export default function Automations() {
|
||||||
const authenticatedData = useAuthenticatedData();
|
const authenticatedData = useAuthenticatedData();
|
||||||
@@ -819,10 +927,31 @@ export default function Automations() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3
|
<div className='py-4 flex justify-between'>
|
||||||
className='text-xl py-4'>
|
<h3
|
||||||
Automations
|
className='text-xl font-bold'>
|
||||||
</h3>
|
Automations
|
||||||
|
</h3>
|
||||||
|
<div className='flex flex-wrap gap-2 items-center md:justify-start justify-end'>
|
||||||
|
{
|
||||||
|
authenticatedData ? (
|
||||||
|
<span className='rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm' ><Envelope className='h-4 w-4 mr-2 inline text-orange-500' shadow-s />{authenticatedData.email}</span>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ipLocationData && (
|
||||||
|
<span className='rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm' ><MapPinSimple className='h-4 w-4 mr-2 inline text-purple-500' />{ipLocationData ? `${ipLocationData.city}, ${ipLocationData.country}` : 'Unknown'}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ipLocationData && (
|
||||||
|
<span className='rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm' ><Clock className='h-4 w-4 mr-2 inline text-green-500' />{ipLocationData ? `${ipLocationData.timezone}` : 'Unknown'}</span>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
showLoginPrompt && (
|
showLoginPrompt && (
|
||||||
<LoginPrompt
|
<LoginPrompt
|
||||||
@@ -830,75 +959,65 @@ export default function Automations() {
|
|||||||
loginRedirectMessage={"Create an account to make your own automation"} />
|
loginRedirectMessage={"Create an account to make your own automation"} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Alert>
|
<Alert className='bg-secondary border-none'>
|
||||||
<Info className="h-4 w-4" />
|
|
||||||
<AlertTitle>How this works!</AlertTitle>
|
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
Automations help you structure your time by automating tasks you do regularly. Build your own, or try out our presets. Get results straight to your inbox.
|
<Lightning weight={'fill'} className='h-4 w-4 text-purple-400 inline' />
|
||||||
<div className='mt-3' />
|
<span className='font-bold'>How it works</span> Automations help you structure your time by automating tasks you do regularly. Build your own, or try out our presets. Get results straight to your inbox.
|
||||||
{
|
|
||||||
authenticatedData ? (
|
|
||||||
<span className='rounded-full text-sm bg-blue-200 dark:bg-blue-600 p-2 m-1' ><Envelope className='h-4 w-4 mr-2 inline' />{authenticatedData.email}</span>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<span> Sign in to create your own automations.</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
ipLocationData && (
|
|
||||||
<span className='rounded-full text-sm bg-purple-200 dark:bg-purple-600 p-2 m-1' ><MapPinSimple className='h-4 w-4 mr-2 inline' />{ipLocationData ? `${ipLocationData.city}, ${ipLocationData.country}` : 'Unknown'}</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
ipLocationData && (
|
|
||||||
<span className='rounded-full text-sm bg-green-200 dark:bg-green-600 p-2 m-1' ><Clock className='h-4 w-4 mr-2 inline' />{ipLocationData ? `${ipLocationData.timezone}` : 'Unknown'}</span>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<h3
|
<div className='flex justify-between py-4'>
|
||||||
className="text-xl py-4">
|
<h3
|
||||||
Your Creations
|
className="text-xl">
|
||||||
</h3>
|
Your Creations
|
||||||
|
</h3>
|
||||||
|
{
|
||||||
|
authenticatedData ? (
|
||||||
|
<Dialog
|
||||||
|
open={isCreating}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsCreating(open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
className='shadow-sm'
|
||||||
|
variant="outline">
|
||||||
|
<Plus className='h-4 w-4 mr-2' />
|
||||||
|
Create Automation
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogTitle>Create Automation</DialogTitle>
|
||||||
|
<EditCard
|
||||||
|
createNew={true}
|
||||||
|
setIsEditing={setIsCreating}
|
||||||
|
isLoggedIn={authenticatedData ? true : false}
|
||||||
|
authenticatedData={authenticatedData}
|
||||||
|
setShowLoginPrompt={setShowLoginPrompt}
|
||||||
|
setUpdatedAutomationData={setNewAutomationData}
|
||||||
|
locationData={ipLocationData} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Button
|
||||||
|
className='shadow-sm'
|
||||||
|
onClick={() => setShowLoginPrompt(true)}
|
||||||
|
variant={'outline'}>
|
||||||
|
<Plus className='h-4 w-4 mr-2' />
|
||||||
|
Create Automation
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<SharedAutomationCard
|
<SharedAutomationCard
|
||||||
|
authenticatedData={authenticatedData}
|
||||||
locationData={ipLocationData}
|
locationData={ipLocationData}
|
||||||
isLoggedIn={authenticatedData ? true : false}
|
isLoggedIn={authenticatedData ? true : false}
|
||||||
setShowLoginPrompt={setShowLoginPrompt}
|
setShowLoginPrompt={setShowLoginPrompt}
|
||||||
setNewAutomationData={setNewAutomationData} />
|
setNewAutomationData={setNewAutomationData} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{
|
|
||||||
authenticatedData ? (
|
|
||||||
<Dialog
|
|
||||||
open={isCreating}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
setIsCreating(open);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogTrigger asChild className='fixed bottom-4 right-4'>
|
|
||||||
<Button variant="default">Create New</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Create Automation</DialogTitle>
|
|
||||||
<EditCard
|
|
||||||
createNew={true}
|
|
||||||
setIsEditing={setIsCreating}
|
|
||||||
isLoggedIn={authenticatedData ? true : false}
|
|
||||||
setShowLoginPrompt={setShowLoginPrompt}
|
|
||||||
setUpdatedAutomationData={setNewAutomationData}
|
|
||||||
locationData={ipLocationData} />
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<Button
|
|
||||||
onClick={() => setShowLoginPrompt(true)}
|
|
||||||
className='fixed bottom-4 right-4' variant={'default'}>
|
|
||||||
Create New
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
((!personalAutomations || personalAutomations.length === 0) && (allNewAutomations.length == 0)) && (
|
((!personalAutomations || personalAutomations.length === 0) && (allNewAutomations.length == 0)) && (
|
||||||
<div>
|
<div>
|
||||||
@@ -918,6 +1037,7 @@ export default function Automations() {
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogTitle>Create Automation</DialogTitle>
|
<DialogTitle>Create Automation</DialogTitle>
|
||||||
<EditCard
|
<EditCard
|
||||||
|
authenticatedData={authenticatedData}
|
||||||
createNew={true}
|
createNew={true}
|
||||||
isLoggedIn={authenticatedData ? true : false}
|
isLoggedIn={authenticatedData ? true : false}
|
||||||
setShowLoginPrompt={setShowLoginPrompt}
|
setShowLoginPrompt={setShowLoginPrompt}
|
||||||
@@ -945,6 +1065,7 @@ export default function Automations() {
|
|||||||
personalAutomations && personalAutomations.map((automation) => (
|
personalAutomations && personalAutomations.map((automation) => (
|
||||||
<AutomationsCard
|
<AutomationsCard
|
||||||
key={automation.id}
|
key={automation.id}
|
||||||
|
authenticatedData={authenticatedData}
|
||||||
automation={automation}
|
automation={automation}
|
||||||
locationData={ipLocationData}
|
locationData={ipLocationData}
|
||||||
isLoggedIn={authenticatedData ? true : false}
|
isLoggedIn={authenticatedData ? true : false}
|
||||||
@@ -952,7 +1073,9 @@ export default function Automations() {
|
|||||||
))}
|
))}
|
||||||
{
|
{
|
||||||
allNewAutomations.map((automation) => (
|
allNewAutomations.map((automation) => (
|
||||||
<AutomationsCard key={automation.id}
|
<AutomationsCard
|
||||||
|
key={automation.id}
|
||||||
|
authenticatedData={authenticatedData}
|
||||||
automation={automation}
|
automation={automation}
|
||||||
locationData={ipLocationData}
|
locationData={ipLocationData}
|
||||||
isLoggedIn={authenticatedData ? true : false}
|
isLoggedIn={authenticatedData ? true : false}
|
||||||
@@ -971,6 +1094,7 @@ export default function Automations() {
|
|||||||
<AutomationsCard
|
<AutomationsCard
|
||||||
setNewAutomationData={setNewAutomationData}
|
setNewAutomationData={setNewAutomationData}
|
||||||
key={automation.id}
|
key={automation.id}
|
||||||
|
authenticatedData={authenticatedData}
|
||||||
automation={automation}
|
automation={automation}
|
||||||
locationData={ipLocationData}
|
locationData={ipLocationData}
|
||||||
isLoggedIn={authenticatedData ? true : false}
|
isLoggedIn={authenticatedData ? true : false}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ interface ShareLinkProps {
|
|||||||
onShare: () => void;
|
onShare: () => void;
|
||||||
buttonVariant?: keyof typeof buttonVariants;
|
buttonVariant?: keyof typeof buttonVariants;
|
||||||
includeIcon?: boolean;
|
includeIcon?: boolean;
|
||||||
|
buttonClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard(text: string) {
|
function copyToClipboard(text: string) {
|
||||||
@@ -36,7 +37,7 @@ export default function ShareLink(props: ShareLinkProps) {
|
|||||||
<DialogTrigger
|
<DialogTrigger
|
||||||
asChild
|
asChild
|
||||||
onClick={props.onShare}>
|
onClick={props.onShare}>
|
||||||
<Button size="sm" className={`px-3`} variant={props.buttonVariant ?? 'default' as const}>
|
<Button size="sm" className={`${props.buttonClassName || 'px-3'}`} variant={props.buttonVariant ?? 'default' as const}>
|
||||||
{
|
{
|
||||||
props.includeIcon && (
|
props.includeIcon && (
|
||||||
<Share className="w-4 h-4 mr-2" />
|
<Share className="w-4 h-4 mr-2" />
|
||||||
|
|||||||
Reference in New Issue
Block a user