fix(dashboard+api): add cors + domain from onboarding

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-12-11 23:35:11 +01:00
parent 5d7bb48b4e
commit cdd13778de
8 changed files with 51 additions and 183 deletions

View File

@@ -50,7 +50,8 @@ const Tracking = ({
defaultValues: {
organization: '',
project: '',
domain: null,
domain: '',
cors: [],
website: false,
app: false,
backend: false,
@@ -75,6 +76,7 @@ const Tracking = ({
useEffect(() => {
if (!isWebsite) {
form.setValue('domain', null);
form.setValue('cors', []);
}
}, [isWebsite, form]);
@@ -156,36 +158,53 @@ const Tracking = ({
>
<AnimateHeight open={isWebsite && !isApp}>
<div className="p-4 pl-14">
<InputWithLabel
label="Domain"
placeholder="Your website address"
{...form.register('domain')}
className="mb-4"
error={form.formState.errors.domain?.message}
onBlur={(e) => {
const value = e.target.value.trim();
if (
value.includes('.') &&
form.getValues().cors.length === 0 &&
!form.formState.errors.domain
) {
form.setValue('cors', [value]);
}
}}
/>
<Controller
name="domain"
name="cors"
control={form.control}
render={({ field }) => (
<WithLabel
label="Domain(s)"
error={form.formState.errors.domain?.message}
>
<WithLabel label="Cors">
<TagInput
{...field}
placeholder="Add a domain"
value={field.value?.split(',') ?? []}
id="Cors"
error={form.formState.errors.cors?.message}
placeholder="Accept events from these domains"
value={field.value ?? []}
renderTag={(tag) =>
tag === '*' ? 'Allow all domains' : tag
tag === '*'
? 'Accept events from any domains'
: tag
}
onChange={(newValue) => {
field.onChange(
newValue
.map((item) => {
const trimmed = item.trim();
if (
trimmed.startsWith('http://') ||
trimmed.startsWith('https://') ||
trimmed === '*'
) {
return trimmed;
}
return `https://${trimmed}`;
})
.join(','),
newValue.map((item) => {
const trimmed = item.trim();
if (
trimmed.startsWith('http://') ||
trimmed.startsWith('https://') ||
trimmed === '*'
) {
return trimmed;
}
return `https://${trimmed}`;
}),
);
}}
/>

View File

@@ -1,56 +0,0 @@
'use client';
import { pushModal, showConfirm } from '@/modals';
import { api } from '@/trpc/client';
import { Edit2Icon, TrashIcon } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import type { IServiceProject } from '@openpanel/db';
import { Button } from '../ui/button';
export function ProjectActions(project: Exclude<IServiceProject, null>) {
const { id } = project;
const router = useRouter();
const deletion = api.project.remove.useMutation({
onSuccess() {
toast('Success', {
description: 'Project deleted successfully.',
});
router.refresh();
},
});
return (
<div className="flex gap-2">
<Button
variant="secondary"
onClick={() => {
pushModal('EditProject', project);
}}
icon={Edit2Icon}
>
Edit project
</Button>
<Button
variant="secondary"
className="text-destructive"
onClick={() => {
showConfirm({
title: 'Delete project',
text: 'This will delete all events for this project. This action cannot be undone.',
onConfirm() {
deletion.mutate({
id,
});
},
});
}}
icon={TrashIcon}
>
Delete project
</Button>
</div>
);
}

View File

@@ -1,28 +0,0 @@
import { formatDate } from '@/utils/date';
import type { ColumnDef } from '@tanstack/react-table';
import type { IServiceProject } from '@openpanel/db';
import { ACTIONS } from '../data-table';
import { ProjectActions } from './project-actions';
export type Project = IServiceProject;
export const columns: ColumnDef<IServiceProject>[] = [
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'createdAt',
header: 'Created at',
cell({ row }) {
const date = row.original.createdAt;
return <div>{formatDate(date)}</div>;
},
},
{
id: ACTIONS,
header: 'Actions',
cell: ({ row }) => <ProjectActions {...row.original} />,
},
];

View File

@@ -1,72 +0,0 @@
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
import type { IServiceProject } from '@openpanel/db';
import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container';
type EditProjectProps = Exclude<IServiceProject, null>;
const validator = z.object({
id: z.string().min(1),
name: z.string().min(1),
});
type IForm = z.infer<typeof validator>;
export default function EditProject({ id, name }: EditProjectProps) {
const router = useRouter();
const { register, handleSubmit, reset, formState } = useForm<IForm>({
resolver: zodResolver(validator),
defaultValues: {
id,
name,
},
});
const mutation = api.project.update.useMutation({
onError: handleError,
onSuccess() {
reset();
router.refresh();
toast('Success', {
description: 'Project updated.',
});
popModal();
},
});
return (
<ModalContent>
<ModalHeader title="Edit project" />
<form
onSubmit={handleSubmit((values) => {
mutation.mutate(values);
})}
>
<InputWithLabel
label="Name"
placeholder="Name"
{...register('name')}
defaultValue={name}
/>
<ButtonContainer>
<Button type="button" variant="outline" onClick={() => popModal()}>
Cancel
</Button>
<Button type="submit" disabled={!formState.isDirty}>
Save
</Button>
</ButtonContainer>
</form>
</ModalContent>
);
}

View File

@@ -20,9 +20,6 @@ const modals = {
EventDetails: dynamic(() => import('./event-details'), {
loading: Loading,
}),
EditProject: dynamic(() => import('./EditProject'), {
loading: Loading,
}),
EditClient: dynamic(() => import('./EditClient'), {
loading: Loading,
}),

View File

@@ -88,12 +88,18 @@ export const onboardingRouter = createTRPCRouter({
});
}
if (input.cors.length === 0 && input.website) {
input.cors.push('*');
}
const project = await db.project.create({
data: {
id: await getId('project', input.project),
name: input.project,
organizationId: organization.id,
types,
domain: input.domain ? stripTrailingSlash(input.domain) : null,
cors: input.cors.map((c) => stripTrailingSlash(c)),
},
});

View File

@@ -9,6 +9,7 @@ import {
getProjectsByOrganizationId,
} from '@openpanel/db';
import { stripTrailingSlash } from '@openpanel/common';
import { zProject } from '@openpanel/validation';
import { getProjectAccess } from '../access';
import { TRPCAccessError } from '../errors';
@@ -50,8 +51,8 @@ export const projectRouter = createTRPCRouter({
name: input.name,
crossDomain: input.crossDomain,
filters: input.filters,
cors: input.cors,
domain: input.domain,
domain: input.domain ? stripTrailingSlash(input.domain) : null,
cors: input.cors?.map((c) => stripTrailingSlash(c)) || [],
},
include: {
clients: {

View File

@@ -113,6 +113,7 @@ export const zOnboardingProject = z
organizationId: z.string().optional(),
project: z.string().min(3),
domain: z.string().url().or(z.literal('').or(z.null())),
cors: z.array(z.string()).default([]),
website: z.boolean(),
app: z.boolean(),
backend: z.boolean(),