Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/lunogram/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func run() error {
logger.Info("initializing cluster")

sched := scheduler.NewController(ctx, logger, conf, journeyStore, usersStore, pub)
lead := leader.NewHandler(sched)
lead := leader.NewHandler(sched, managementStore, logger)
cons, err := consensus.NewCluster(ctx, logger, conf)
if err != nil {
return err
Expand Down
36 changes: 36 additions & 0 deletions console/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
self.addEventListener('push', function(event) {
if (!event.data) return;

try {
const data = event.data.json();
const title = data.title || 'Notification';
const options = {
body: data.body,
icon: data.icon,
badge: data.badge,
image: data.image,
data: data.data || {}
};

event.waitUntil(
self.registration.showNotification(title, options)
);
} catch (e) {
// If not JSON, show simple text
event.waitUntil(
self.registration.showNotification('Notification', {
body: event.data.text()
})
);
}
});

self.addEventListener('notificationclick', function(event) {
event.notification.close();
// We can handle click events here, like opening a specific URL
if (event.notification.data && event.notification.data.url) {
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
}
});
20 changes: 20 additions & 0 deletions console/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,26 @@
})
.then((r) => r.data),
},

push: {
getVapidPublicKey: async () =>
await client
.get<{ public_key: string }>("/admin/push/vapid-public-key")
.then((r) => r.data),
},

devices: {
register: async (
projectId: UUID,
params: {
device_id: string
external_id?: string
anonymous_id?: string
os?: "web" | "ios" | "android"
push_subscription: any

Check warning on line 680 in console/src/api.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
},
) => await client.post(`${projectUrl(projectId)}/devices`, params).then((r) => r.data),
},
}

export default api
Expand Down
152 changes: 151 additions & 1 deletion console/src/components/schema-fields.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useEffect, useMemo } from "react"
import { useEffect, useMemo, useRef } from "react"
import type { UseFormReturn } from "react-hook-form"
import { snakeToTitle } from "@/utils"

import { Input } from "@/components/ui/input"
import { TemplateInput } from "@/components/ui/template-input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import { Button } from "@/components/ui/button"
import {
Select,
SelectContent,
Expand All @@ -18,6 +19,135 @@
import { KeyValueEditor } from "@/components/ui/key-value-editor"
import type { VariableGroup } from "@/views/journey/JourneyVariableContext"

interface StringFieldWithFileProps {
fieldKey: string
item: Schema
value: Record<string, unknown>
set: (key: string, v: unknown) => void
required: boolean
fieldTitle: string
variables?: VariableGroup[]
}

function StringFieldWithFile({
fieldKey,
item,
value,
set,
required,
fieldTitle,
variables,
}: StringFieldWithFileProps) {
const fileInputRef = useRef<HTMLInputElement>(null)
const currentValue = (value[fieldKey] as string) ?? ""

const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return

const reader = new FileReader()
reader.onload = () => {
let result = reader.result as string
if (item.requireBase64) {
result = btoa(result)
}
set(fieldKey, result)
if (fileInputRef.current) {
fileInputRef.current.value = ""
}
}
reader.readAsText(file)
}

const handleBase64Encode = () => {
if (currentValue && !isBase64(currentValue)) {
set(fieldKey, btoa(currentValue))
}
}

const isBase64 = (str: string): boolean => {
if (!str) return false
try {
return btoa(atob(str)) === str
} catch {
return false
}
}

const useTextarea = (item.minLength ?? 0) >= 80
const useTemplateInput =
!useTextarea &&
!item.fileUpload &&
variables &&
variables.some((g) => g.variables.length > 0)

return (
<div className="grid gap-1.5">
<Label className="inline-flex items-center gap-1 text-sm font-medium">
{fieldTitle}
{required && <span className="text-destructive">*</span>}
</Label>
{item.description && (
<p className="text-sm text-muted-foreground">{item.description}</p>
)}
<div className="flex gap-2">
<div className="flex-1">
{useTextarea ? (
<Textarea
value={currentValue}
onChange={(e) => set(fieldKey, e.target.value)}
placeholder={item.preview}
/>
) : useTemplateInput ? (
<TemplateInput
value={currentValue}
onChange={(val) => set(fieldKey, val)}
placeholder={item.preview}
variables={variables}
/>
) : (
<Input
type={item.format === "password" ? "password" : "text"}
value={currentValue}
onChange={(e) => set(fieldKey, e.target.value)}
placeholder={item.preview}
/>
)}
</div>
{item.fileUpload && (
<>
<input
type="file"
ref={fileInputRef}
accept={item.fileAccept}
className="hidden"
onChange={handleFileUpload}
/>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => fileInputRef.current?.click()}
>
Upload File
</Button>
</>
)}
{item.requireBase64 && !item.fileUpload && currentValue && !isBase64(currentValue) && (

Check warning on line 136 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Replace `·!item.fileUpload·&&·currentValue·&&` with `⏎····················!item.fileUpload·&&⏎····················currentValue·&&⏎···················`
<Button

Check warning on line 137 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
type="button"

Check warning on line 138 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
variant="outline"

Check warning on line 139 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
size="sm"

Check warning on line 140 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
onClick={handleBase64Encode}

Check warning on line 141 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
>

Check warning on line 142 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
Base64 Encode

Check warning on line 143 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
</Button>

Check warning on line 144 in console/src/components/schema-fields.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `····`
)}
</div>
</div>
)
}

export interface SchemaProperty {
name: string
schema: Schema
Expand All @@ -35,6 +165,9 @@
format?: string
order?: number
preview?: string
fileUpload?: boolean
fileAccept?: string
requireBase64?: boolean
}

/**
Expand Down Expand Up @@ -183,6 +316,23 @@

// string / number
if (item.type === "string" || item.type === "number") {
// String fields with file upload or base64 encoding
if (item.type === "string" && (item.fileUpload || item.requireBase64)) {
return (
<StringFieldWithFile
key={key}
fieldKey={key}
item={item}
value={value}
set={set}
required={required ?? false}
fieldTitle={fieldTitle}
variables={variables}
/>
)
}

// Regular string/number fields
const useTextarea = (item.minLength ?? 0) >= 80
const useTemplateInput =
item.type === "string" &&
Expand Down
Loading
Loading