fix(dashboard+api): add cors + domain from onboarding
This commit is contained in:
@@ -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}`;
|
||||
}),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />,
|
||||
},
|
||||
];
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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)),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user