Initial Commit

This commit is contained in:
Dries Augustyns
2024-07-23 13:49:48 +02:00
commit 91a8840c3c
191 changed files with 36022 additions and 0 deletions
@@ -0,0 +1,5 @@
import {atom} from 'jotai';
export const atomActiveProject = atom<string | null>(
typeof window !== 'undefined' ? window.localStorage.getItem('project') : null,
);
+4
View File
@@ -0,0 +1,4 @@
export const API_URI = process.env.NEXT_PUBLIC_API_URI ?? 'http://localhost:8080';
export const AWS_REGION = process.env.NEXT_PUBLIC_AWS_REGION;
export const NO_AUTH_ROUTES = ['/auth/signup', '/auth/login', '/auth/reset', '/unsubscribe/[id]', '/subscribe/[id]'];
@@ -0,0 +1,43 @@
import useSWR from 'swr';
import {Action, Email, Event, Task, Template, Trigger} from '@prisma/client';
import {useActiveProject} from './projects';
/**
*
* @param id
*/
export function useAction(id: string) {
return useSWR(`/v1/actions/${id}`);
}
/**
*
* @param id
*/
export function useRelatedActions(id: string) {
return useSWR<
(Action & {
events: Event[];
notevents: Event[];
triggers: Trigger[];
emails: Email[];
template: Template;
})[]
>(`/v1/actions/${id}/related`);
}
/**
*
*/
export function useActions() {
const activeProject = useActiveProject();
return useSWR<
(Action & {
triggers: Trigger[];
template: Template;
emails: Email[];
tasks: Task[];
})[]
>(activeProject ? `/projects/id/${activeProject.id}/actions` : null);
}
@@ -0,0 +1,34 @@
import {useActiveProject} from './projects';
import useSWR from 'swr';
/**
* @param method
*/
export function useAnalytics(method?: 'week' | 'month' | 'year') {
const activeProject = useActiveProject();
return useSWR<{
contacts: {
timeseries: {
day: Date;
count: number;
}[];
subscribed: number;
unsubscribed: number;
};
emails: {
total: number;
bounced: number;
opened: number;
complaint: number;
totalPrev: number;
bouncedPrev: number;
openedPrev: number;
complaintPrev: number;
};
clicks: {
actions: {link: string; name: string; count: number}[];
};
}>(activeProject ? `/projects/id/${activeProject.id}/analytics?method=${method ?? 'week'}` : null);
}
@@ -0,0 +1,33 @@
import useSWR from 'swr';
import {Campaign} from '@prisma/client';
import {useActiveProject} from './projects';
/**
*
* @param id
*/
export function useCampaign(id: string) {
return useSWR(`/v1/campaigns/${id}`);
}
/**
*
*/
export function useCampaigns() {
const activeProject = useActiveProject();
return useSWR<
(Campaign & {
emails: {
id: string;
status: string;
}[];
tasks: {
id: string;
}[];
recipients: {
id: string;
}[];
})[]
>(activeProject ? `/projects/id/${activeProject.id}/campaigns` : null);
}
@@ -0,0 +1,106 @@
import useSWR from 'swr';
import {Action, Contact, Email, Event, Project, Trigger} from '@prisma/client';
import {useActiveProject} from './projects';
export interface WithProject {
id: string;
withProject: true;
}
export interface WithoutProject {
id: string;
withProject?: false;
}
export type WithOrWithoutProject<T extends WithProject | WithoutProject> = T extends WithProject
?
| (Contact & {
emails: Email[];
triggers: (Trigger & {
event: Event | null;
action: Action | null;
})[];
project: Project;
})
| null
:
| (Contact & {
emails: Email[];
triggers: (Trigger & {
event: Event | null;
action: Action | null;
})[];
})
| null;
/**
*
* @param id.id
* @param id
* @param id.withProject
*/
export function useContact<T extends WithProject | WithoutProject>({id, withProject = false}: T) {
return useSWR<WithOrWithoutProject<T>>(withProject ? `/v1/contacts/${id}?withProject=true` : `/v1/contacts/${id}`);
}
/**
*
* @param page
*/
export function useContacts(page: number) {
const activeProject = useActiveProject();
return useSWR<{
contacts: (Contact & {
triggers: Trigger[];
})[];
count: number;
}>(activeProject ? `/projects/id/${activeProject.id}/contacts?page=${page}` : null);
}
/**
*
*/
export function useContactsCount() {
const activeProject = useActiveProject();
return useSWR<number>(activeProject ? `/projects/id/${activeProject.id}/contacts/count` : null);
}
/**
*
*/
export function useContactMetadata() {
const activeProject = useActiveProject();
return useSWR<string[]>(activeProject ? `/projects/id/${activeProject.id}/contacts/metadata` : null);
}
/**
*
* @param query
*/
export function searchContacts(query: string | undefined) {
const activeProject = useActiveProject();
if (!query) {
return useSWR<{
contacts: (Contact & {
triggers: Trigger[];
emails: Email[];
})[];
count: number;
}>(activeProject ? `/projects/id/${activeProject.id}/contacts` : null);
}
return useSWR<{
contacts: (Contact & {
triggers: Trigger[];
emails: Email[];
})[];
count: number;
}>(activeProject ? `/projects/id/${activeProject.id}/contacts/search?query=${query}` : null, {
revalidateOnFocus: false,
refreshInterval: 0,
});
}
@@ -0,0 +1,21 @@
import {useActiveProject} from './projects';
import useSWR from 'swr';
import {Email} from '@prisma/client';
/**
*
*/
export function useEmails() {
const activeProject = useActiveProject();
return useSWR<Email[]>(activeProject ? `/projects/id/${activeProject.id}/emails` : null);
}
/**
*
*/
export function useEmailsCount() {
const activeProject = useActiveProject();
return useSWR<number>(activeProject ? `/projects/id/${activeProject.id}/emails/count` : null);
}
@@ -0,0 +1,29 @@
import useSWR from 'swr';
import {Event} from '@prisma/client';
import {useActiveProject} from './projects';
/**
*
*/
export function useEvents() {
const activeProject = useActiveProject();
return useSWR<
(Event & {
triggers: {
id: string;
createdAt: Date;
contactId: string;
}[];
})[]
>(activeProject ? `/projects/id/${activeProject.id}/events` : null);
}
/**
*
*/
export function useEventsWithoutTriggers() {
const activeProject = useActiveProject();
return useSWR<Event[]>(activeProject ? `/projects/id/${activeProject.id}/events?triggers=false` : null);
}
@@ -0,0 +1,82 @@
import {Action, Contact, Email, Event, Project, Role} from '@prisma/client';
import {useAtom} from 'jotai';
import useSWR from 'swr';
import {atomActiveProject} from '../atoms/project';
/**
*
*/
export function useProjects() {
return useSWR<Project[]>('/users/@me/projects');
}
/**
*
*/
export function useActiveProject(): Project | null {
const [activeProject, setActiveProject] = useAtom(atomActiveProject);
const {data: projects} = useProjects();
if (!projects) {
return null;
}
if (activeProject && !projects.find(project => project.id === activeProject)) {
setActiveProject(null);
window.localStorage.removeItem('project');
}
if (!activeProject && projects.length > 0) {
setActiveProject(projects[0].id);
window.localStorage.setItem('project', projects[0].id);
}
return projects.find(project => project.id === activeProject) ?? null;
}
/**
*
*/
export function useActiveProjectMemberships() {
const activeProject = useActiveProject();
return useSWR<
{
userId: string;
email: string;
role: Role;
}[]
>(activeProject ? `/projects/id/${activeProject.id}/memberships` : null);
}
/**
*
*/
export function useActiveProjectFeed(page: number) {
const activeProject = useActiveProject();
return useSWR<
(
| {
createdAt: Date;
contact: Contact;
event: Event | null;
action: Action | null;
}
| ({
contact: Contact;
} & Email)
)[]
>(activeProject ? `/projects/id/${activeProject.id}/feed?page=${page}` : null);
}
/**
*
*/
export function useActiveProjectVerifiedIdentity() {
const activeProject = useActiveProject();
return useSWR<{
tokens: string[];
}>(activeProject ? `/identities/id/${activeProject.id}` : null);
}
@@ -0,0 +1,24 @@
import useSWR from 'swr';
import {Action, Template} from '@prisma/client';
import {useActiveProject} from './projects';
/**
*
* @param id
*/
export function useTemplate(id: string) {
return useSWR(`/v1/templates/${id}`);
}
/**
*
*/
export function useTemplates() {
const activeProject = useActiveProject();
return useSWR<
(Template & {
actions: Action[];
})[]
>(activeProject ? `/projects/id/${activeProject.id}/templates` : null);
}
@@ -0,0 +1,9 @@
import useSWR from 'swr';
/**
* Fetch the current user. undefined means loading, null means logged out
*
*/
export function useUser() {
return useSWR('/users/@me', {shouldRetryOnError: false});
}
+67
View File
@@ -0,0 +1,67 @@
import {API_URI} from './constants';
import {infer as ZodInfer, ZodSchema} from 'zod';
interface Json {
[x: string]: string | number | boolean | Date | Json | JsonArray;
}
type JsonArray = (string | number | boolean | Date | Json | JsonArray)[];
interface TypedSchema extends ZodSchema {
_type: any;
}
export class network {
/**
* Fetcher function that includes toast support
* @param method Request method
* @param path Request endpoint or path
* @param body Request body
*/
public static async fetch<T, Schema extends TypedSchema | void = void>(
method: 'GET' | 'PUT' | 'POST' | 'DELETE',
path: string,
body?: Schema extends TypedSchema ? ZodInfer<Schema> : never,
): Promise<T> {
const url = path.startsWith('http') ? path : API_URI + path;
const response = await fetch(url, {
method,
body: body && JSON.stringify(body),
headers: body && {'Content-Type': 'application/json'},
credentials: 'include',
});
const res = await response.json();
if (response.status >= 400) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
throw new Error(res?.message ?? 'Something went wrong!');
}
return res;
}
public static async mock<T, Schema extends TypedSchema | void = void>(
key: string,
method: 'GET' | 'PUT' | 'POST' | 'DELETE',
path: string,
body?: Schema extends TypedSchema ? ZodInfer<Schema> : never,
): Promise<T> {
const url = path.startsWith('http') ? path : API_URI + path;
const response = await fetch(url, {
method,
body: body && JSON.stringify(body),
headers: {'Content-Type': 'application/json', 'Authorization': `Bearer ${key}`},
credentials: 'include',
});
const res = await response.json();
if (response.status >= 400) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
throw new Error(res?.message ?? 'Something went wrong!');
}
return res;
}
}