Initial Commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import {atom} from 'jotai';
|
||||
|
||||
export const atomActiveProject = atom<string | null>(
|
||||
typeof window !== 'undefined' ? window.localStorage.getItem('project') : null,
|
||||
);
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user