added event helpers to ui libary, added Badge component, reordered Dashboard Components, splitted Event Admin page

This commit is contained in:
PxlLoewe
2025-03-07 14:16:19 -07:00
parent c1f1ad47b7
commit 829e78a47d
25 changed files with 465 additions and 355 deletions

14
.vscode/settings.json vendored
View File

@@ -19,5 +19,17 @@
}, },
"[xml]": { "[xml]": {
"editor.defaultFormatter": "redhat.vscode-xml" "editor.defaultFormatter": "redhat.vscode-xml"
} },
"sqltools.connections": [
{
"previewLimit": 50,
"server": "localhost",
"port": 5432,
"driver": "PostgreSQL",
"name": "Persistant-Data",
"database": "var",
"username": "persistant-data",
"password": "persistant-data-pw"
}
]
} }

View File

@@ -0,0 +1 @@
SELECT * FROM users

View File

@@ -0,0 +1,28 @@
import { Badge } from "@repo/ui";
import { Award } from "lucide-react";
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
export const Badges = async () => {
const session = await getServerSession();
if (!session) return null;
return (
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
<div className="card-body">
<h2 className="card-title justify-between">
<span className="card-title">
<Award className="w-4 h-4" /> Verdiente Abzeichen
</span>
</h2>
{session.user.badges.map((badge) => {
return (
<div className="badge badge-primary badge-outline">
<Badge name={badge} />
</div>
);
})}
</div>
</div>
);
};

View File

@@ -1,6 +1,8 @@
import { getServerSession } from "../../api/auth/[...nextauth]/auth"; import { getServerSession } from "../../api/auth/[...nextauth]/auth";
import { PrismaClient } from "@repo/db"; import { PrismaClient } from "@repo/db";
import { KursItem } from "../events/_components/item"; import { KursItem } from "../events/_components/item";
import { RocketIcon } from "lucide-react";
import { eventCompleted } from "@repo/ui";
export default async () => { export default async () => {
const prisma = new PrismaClient(); const prisma = new PrismaClient();
@@ -39,18 +41,37 @@ export default async () => {
}, },
}); });
const filteredEvents = events.filter((event) => {
console.log;
if (eventCompleted(event, event.participants[0])) return false;
if (
event.type === "OBLIGATED_COURSE" &&
!eventCompleted(event, event.participants[0])
)
return true;
return false;
});
if (!filteredEvents.length) return null;
return ( return (
<div className="grid grid-cols-6 gap-4"> <div>
{events.map((event) => { <div className="col-span-full">
return ( <p className="text-xl font-semibold text-left flex items-center gap-2 mb-2 mt-5">
<KursItem <RocketIcon className="w-4 h-4" /> Laufende Events & Kurse
selectedAppointments={userAppointments} </p>
user={user} </div>
event={event} <div className="grid grid-cols-6 gap-4">
key={event.id} {filteredEvents.map((event) => {
/> return (
); <KursItem
})} selectedAppointments={userAppointments}
user={user}
event={event}
key={event.id}
/>
);
})}
</div>
</div> </div>
); );
}; };

View File

@@ -0,0 +1,203 @@
import { DateInput } from "../../../../_components/ui/DateInput";
import { zodResolver } from "@hookform/resolvers/zod";
import { Event, Participant, Prisma } from "@repo/db";
import {
EventAppointmentOptionalDefaults,
EventAppointmentOptionalDefaultsSchema,
ParticipantOptionalDefaultsSchema,
} from "@repo/db/zod";
import { useSession } from "next-auth/react";
import { Controller, useForm } from "react-hook-form";
import { deleteAppoinement, upsertAppointment } from "../action";
import { Button } from "../../../../_components/ui/Button";
import {
PaginatedTable,
PaginatedTableRef,
} from "../../../../_components/PaginatedTable";
import { Ref, RefObject, useRef } from "react";
import { CellContext } from "@tanstack/react-table";
import { upsertParticipant } from "../../../events/actions";
import { Switch } from "../../../../_components/ui/Switch";
interface AppointmentModalProps {
event?: Event;
ref: RefObject<HTMLDialogElement | null>;
appointmentsTableRef: React.RefObject<PaginatedTableRef>;
}
export const AppointmentModal = ({
event,
ref,
appointmentsTableRef,
}: AppointmentModalProps) => {
const { data: session } = useSession();
const appointmentForm = useForm<EventAppointmentOptionalDefaults>({
resolver: zodResolver(EventAppointmentOptionalDefaultsSchema),
defaultValues: {
eventId: event?.id,
presenterId: session?.user?.id,
},
});
const participantTableRef = useRef<PaginatedTableRef>(null);
const participantForm = useForm<Participant>({
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
});
return (
<dialog ref={ref} className="modal">
<div className="modal-box">
<form method="dialog">
{/* if there is a button in form, it will close the modal */}
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
</button>
</form>
<h3 className="font-bold text-lg">
Termin {appointmentForm.watch("id")}
</h3>
<form
onSubmit={appointmentForm.handleSubmit(async (values) => {
if (!event) return;
const createdAppointment = await upsertAppointment(values);
ref.current?.close();
appointmentsTableRef.current?.refresh();
})}
className="flex flex-col"
>
<DateInput control={appointmentForm.control} name="appointmentDate" />
{/* <Input
form={appointmentForm}
type="datetime-local"
label="Datum"
name="appointmentDate"
formOptions={{
valueAsDate: true,
}}
/> */}
<div>
{appointmentForm.watch("id") && (
<PaginatedTable
ref={participantTableRef}
columns={[
{
accessorKey: "User.firstname",
header: "Vorname",
},
{
accessorKey: "User.lastname",
header: "Nachname",
},
{
header: "Aktion",
cell: ({ row }: CellContext<Participant, any>) => {
return (
<>
<button
onClick={() => {
participantForm.reset(row.original);
}}
className="btn btn-outline btn-sm"
>
anzeigen
</button>
{!row.original.attended &&
event?.hasPresenceEvents && (
<button
type="button"
onSubmit={() => {}}
onClick={async () => {
await upsertParticipant({
eventId: event!.id,
userId: participantForm.watch("userId"),
attended: true,
});
participantTableRef.current?.refresh();
}}
className="btn btn-outline btn-info btn-sm"
>
Anwesend
</button>
)}
</>
);
},
},
]}
prismaModel={"participant"}
filter={{
eventAppointmentId: appointmentForm.watch("id"),
}}
include={{ User: true }}
leftOfPagination={
<div className="space-x-1">
<Button type="submit" className="btn btn-primary">
Speichern
</Button>
{appointmentForm.watch("id") && (
<Button
type="button"
onSubmit={() => {}}
onClick={async () => {
await deleteAppoinement(appointmentForm.watch("id")!);
ref.current?.close();
appointmentsTableRef.current?.refresh();
}}
className="btn btn-error btn-outline"
>
Löschen
</Button>
)}
</div>
}
/>
)}
</div>
<div className="modal-action"></div>
</form>
{participantForm.watch("id") && (
<form
onSubmit={participantForm.handleSubmit(async (data) => {
await upsertParticipant({
...data,
statusLog: data.statusLog as Prisma.InputJsonValue[],
});
participantTableRef.current?.refresh();
})}
className="space-y-1"
>
<h1 className="text-2xl">Teilnehmer bearbeiten</h1>
<Switch
form={participantForm}
name="appointmentCancelled"
label="Termin abgesagt"
/>
<Switch
form={participantForm}
name="finisherMoodleCurseCompleted"
label="Abschluss-Moodle kurs abgeschlossen"
/>
{event?.hasPresenceEvents && (
<Switch
form={participantForm}
name="attended"
label="An Presenstermin teilgenommen"
/>
)}
<div className="flex flex-col">
<h3 className="text-xl">Verlauf</h3>
{participantForm.watch("statusLog").map((s) => {
return (
<div className="flex justify-between">
<p>{(s as any).event}</p>
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
</div>
);
})}
</div>
<Button>Speichern</Button>
</form>
)}
</div>
</dialog>
);
};

View File

@@ -38,10 +38,7 @@ import {
import { Select } from "../../../../_components/ui/Select"; import { Select } from "../../../../_components/ui/Select";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { MarkdownEditor } from "../../../../_components/ui/MDEditor"; import { MarkdownEditor } from "../../../../_components/ui/MDEditor";
import { CellContext } from "@tanstack/react-table"; import { AppointmentModal } from "./AppointmentModal";
import { upsertParticipant } from "../../../events/actions";
import { de } from "date-fns/locale";
registerLocale("de", de);
export const Form = ({ event }: { event?: Event }) => { export const Form = ({ event }: { event?: Event }) => {
const { data: session } = useSession(); const { data: session } = useSession();
@@ -66,177 +63,10 @@ export const Form = ({ event }: { event?: Event }) => {
}); });
return ( return (
<> <>
<dialog ref={appointmentModal} className="modal"> <AppointmentModal
<div className="modal-box"> ref={appointmentModal}
<form method="dialog"> appointmentsTableRef={appointmentsTableRef}
{/* if there is a button in form, it will close the modal */} />
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
</button>
</form>
<h3 className="font-bold text-lg">
Termin {appointmentForm.watch("id")}
</h3>
<form
onSubmit={appointmentForm.handleSubmit(async (values) => {
if (!event) return;
const createdAppointment = await upsertAppointment(values);
appointmentModal.current?.close();
appointmentsTableRef.current?.refresh();
})}
className="flex flex-col"
>
<Controller
control={appointmentForm.control}
name="appointmentDate"
render={({ field }) => (
<DatePicker
locale={"de"}
showTimeCaption
showTimeInput
showTimeSelect
placeholderText="Select date"
onChange={(date) => field.onChange(date)}
selected={field.value}
/>
)}
/>
{/* <Input
form={appointmentForm}
type="datetime-local"
label="Datum"
name="appointmentDate"
formOptions={{
valueAsDate: true,
}}
/> */}
<div>
{appointmentForm.watch("id") && (
<PaginatedTable
ref={participantTableRef}
columns={[
{
accessorKey: "User.firstname",
header: "Vorname",
},
{
accessorKey: "User.lastname",
header: "Nachname",
},
{
header: "Aktion",
cell: ({ row }: CellContext<Participant, any>) => {
return (
<>
<button
onClick={() => {
participantForm.reset(row.original);
}}
className="btn btn-outline btn-sm"
>
anzeigen
</button>
{!row.original.attended &&
event?.hasPresenceEvents && (
<button
type="button"
onSubmit={() => {}}
onClick={async () => {
await upsertParticipant({
eventId: event!.id,
userId: participantForm.watch("userId"),
attended: true,
});
participantTableRef.current?.refresh();
}}
className="btn btn-outline btn-info btn-sm"
>
Anwesend
</button>
)}
</>
);
},
},
]}
prismaModel={"participant"}
filter={{
eventAppointmentId: appointmentForm.watch("id"),
}}
include={{ User: true }}
leftOfPagination={
<div className="space-x-1">
<Button type="submit" className="btn btn-primary">
Speichern
</Button>
{appointmentForm.watch("id") && (
<Button
type="button"
onSubmit={() => {}}
onClick={async () => {
await deleteAppoinement(
appointmentForm.watch("id")!,
);
appointmentModal.current?.close();
appointmentsTableRef.current?.refresh();
}}
className="btn btn-error btn-outline"
>
Löschen
</Button>
)}
</div>
}
/>
)}
</div>
<div className="modal-action"></div>
</form>
{participantForm.watch("id") && (
<form
onSubmit={participantForm.handleSubmit(async (data) => {
await upsertParticipant({
...data,
statusLog: data.statusLog as Prisma.InputJsonValue[],
});
participantTableRef.current?.refresh();
})}
className="space-y-1"
>
<h1 className="text-2xl">Teilnehmer bearbeiten</h1>
<Switch
form={participantForm}
name="appointmentCancelled"
label="Termin abgesagt"
/>
<Switch
form={participantForm}
name="finisherMoodleCurseCompleted"
label="Abschluss-Moodle kurs abgeschlossen"
/>
{event?.hasPresenceEvents && (
<Switch
form={participantForm}
name="attended"
label="An Presenstermin teilgenommen"
/>
)}
<div className="flex flex-col">
<h3 className="text-xl">Verlauf</h3>
{participantForm.watch("statusLog").map((s) => {
return (
<div className="flex justify-between">
<p>{(s as any).event}</p>
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
</div>
);
})}
</div>
<Button>Speichern</Button>
</form>
)}
</div>
</dialog>
<form <form
onSubmit={form.handleSubmit(async (values) => { onSubmit={form.handleSubmit(async (values) => {
setLoading(true); setLoading(true);
@@ -322,92 +152,94 @@ export const Form = ({ event }: { event?: Event }) => {
/> />
</div> </div>
</div> </div>
<div className="card bg-base-200 shadow-xl col-span-6"> {form.watch("hasPresenceEvents") ? (
<div className="card-body"> <div className="card bg-base-200 shadow-xl col-span-6">
<div className="flex justify-between"> <div className="card-body">
<h2 className="card-title"> <div className="flex justify-between">
<Calendar className="w-5 h-5" /> Termine <h2 className="card-title">
</h2> <Calendar className="w-5 h-5" /> Termine
{event && ( </h2>
<button {event && (
className="btn btn-primary btn-outline" <button
onClick={() => { className="btn btn-primary btn-outline"
appointmentModal.current?.showModal(); onClick={() => {
appointmentForm.reset({ appointmentModal.current?.showModal();
id: undefined, appointmentForm.reset({
}); id: undefined,
}} });
> }}
Hinzufügen >
</button> Hinzufügen
)} </button>
</div> )}
</div>
<PaginatedTable <PaginatedTable
ref={appointmentsTableRef} ref={appointmentsTableRef}
prismaModel={"eventAppointment"} prismaModel={"eventAppointment"}
filter={{ filter={{
eventId: event?.id, eventId: event?.id,
}} }}
include={{ include={{
Presenter: true, Presenter: true,
Participants: true, Participants: true,
}} }}
columns={[ columns={[
{ {
header: "Datum", header: "Datum",
accessorKey: "appointmentDate", accessorKey: "appointmentDate",
accessorFn: (date) => accessorFn: (date) =>
new Date(date.appointmentDate).toLocaleDateString(), new Date(date.appointmentDate).toLocaleDateString(),
},
{
header: "Presenter",
accessorKey: "presenter",
cell: ({ row }) => (
<div className="flex items-center">
<span className="ml-2">
{(row.original as any).Presenter.firstname}{" "}
{(row.original as any).Presenter.lastname}
</span>
</div>
),
},
{
header: "Teilnehmer",
accessorKey: "Participants",
cell: ({ row }) => (
<div className="flex items-center">
<UserIcon className="w-5 h-5" />
<span className="ml-2">
{row.original.Participants.length}
</span>
</div>
),
},
{
header: "Aktionen",
cell: ({ row }) => {
return (
<div className="flex gap-2">
<button
onSubmit={() => false}
type="button"
onClick={() => {
appointmentForm.reset(row.original);
appointmentModal.current?.showModal();
}}
className="btn btn-sm btn-outline"
>
Bearbeiten
</button>
</div>
);
}, },
}, {
]} header: "Presenter",
/> accessorKey: "presenter",
cell: ({ row }) => (
<div className="flex items-center">
<span className="ml-2">
{(row.original as any).Presenter.firstname}{" "}
{(row.original as any).Presenter.lastname}
</span>
</div>
),
},
{
header: "Teilnehmer",
accessorKey: "Participants",
cell: ({ row }) => (
<div className="flex items-center">
<UserIcon className="w-5 h-5" />
<span className="ml-2">
{row.original.Participants.length}
</span>
</div>
),
},
{
header: "Aktionen",
cell: ({ row }) => {
return (
<div className="flex gap-2">
<button
onSubmit={() => false}
type="button"
onClick={() => {
appointmentForm.reset(row.original);
appointmentModal.current?.showModal();
}}
className="btn btn-sm btn-outline"
>
Bearbeiten
</button>
</div>
);
},
},
]}
/>
</div>
</div> </div>
</div> ) : null}
<div className="card bg-base-200 shadow-xl col-span-6"> <div className="card bg-base-200 shadow-xl col-span-6">
<div className="card-body "> <div className="card-body ">
<div className="flex w-full gap-4"> <div className="flex w-full gap-4">

View File

@@ -8,7 +8,7 @@ import {
import { Event, EventAppointment, Participant, prisma, User } from "@repo/db"; import { Event, EventAppointment, Participant, prisma, User } from "@repo/db";
import { cn } from "../../../../helper/cn"; import { cn } from "../../../../helper/cn";
import { inscribeToMoodleCourse, upsertParticipant } from "../actions"; import { inscribeToMoodleCourse, upsertParticipant } from "../actions";
import { Clock10Icon, Cross } from "lucide-react"; import { Check, Clock10Icon, Cross, EyeIcon } from "lucide-react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { import {
EventAppointmentOptionalDefaults, EventAppointmentOptionalDefaults,
@@ -22,6 +22,7 @@ import { Select } from "../../../_components/ui/Select";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { JsonArray } from "../../../../../../packages/database/generated/client/runtime/library"; import { JsonArray } from "../../../../../../packages/database/generated/client/runtime/library";
import { eventCompleted } from "@repo/ui";
interface ModalBtnProps { interface ModalBtnProps {
title: string; title: string;
@@ -90,10 +91,25 @@ const ModalBtn = ({
className={cn( className={cn(
"btn btn-outline btn-info btn-wide", "btn btn-outline btn-info btn-wide",
event.type === "OBLIGATED_COURSE" && "btn-secondary", event.type === "OBLIGATED_COURSE" && "btn-secondary",
eventCompleted(event, participant) && "btn-success",
)} )}
onClick={openModal} onClick={openModal}
> >
<EnterIcon /> Anmelden {participant && !eventCompleted(event, participant) && (
<>
<EyeIcon /> Anzeigen
</>
)}
{!participant && (
<>
<EnterIcon /> Anmelden
</>
)}
{eventCompleted(event, participant) && (
<>
<Check /> Abgeschlossen
</>
)}
</button> </button>
<dialog id={modalId} className="modal"> <dialog id={modalId} className="modal">
<div className="modal-box"> <div className="modal-box">

View File

@@ -3,6 +3,7 @@ import { ArrowRight, NotebookText, Award, RocketIcon } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import Events from "./_components/Events"; import Events from "./_components/Events";
import StatsClientWrapper from "./_components/StatsClientWrapper"; import StatsClientWrapper from "./_components/StatsClientWrapper";
import { Badges } from "./_components/Badges";
/* /*
✔️ Einlog-Zeit ✔️ Einlog-Zeit
@@ -36,22 +37,7 @@ export default function Home() {
<Logbook /> <Logbook />
</div> </div>
</div> </div>
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3"> <Badges />
<div className="card-body">
<h2 className="card-title justify-between">
<span className="card-title">
<Award className="w-4 h-4" /> Verdiente Abzeichen
</span>
</h2>
Badges
</div>
</div>
</div>
<div className="col-span-full">
<p className="text-xl font-semibold text-left flex items-center gap-2 mb-2 mt-5">
<RocketIcon className="w-4 h-4" /> Laufende Events & Kurse
</p>
</div> </div>
<Events /> <Events />
</div> </div>

View File

@@ -0,0 +1,37 @@
import DatePicker, { DatePickerProps, registerLocale } from "react-datepicker";
import {
Control,
Controller,
FieldValues,
Path,
PathValue,
} from "react-hook-form";
import { de } from "date-fns/locale";
registerLocale("de", de);
interface DateInputProps<T extends FieldValues>
extends Omit<DatePickerProps, "onChange" | "selected"> {
control: Control<T>;
name: Path<T>;
}
export const DateInput = <T extends FieldValues>({
control,
name,
...props
}: DateInputProps<T>) => {
return (
<Controller
control={control}
name={name}
render={({ field }) => (
<DatePicker
locale={"de"}
onChange={(date) => field.onChange(date)}
selected={field.value}
{...(props as any)}
/>
)}
/>
);
};

View File

@@ -3,9 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"exports": { "exports": {
"./button": "./src/button.tsx", ".": "./src/index.ts"
"./card": "./src/card.tsx",
"./code": "./src/code.tsx"
}, },
"scripts": { "scripts": {
"lint": "eslint . --max-warnings 0", "lint": "eslint . --max-warnings 0",
@@ -14,6 +12,7 @@
}, },
"devDependencies": { "devDependencies": {
"@repo/eslint-config": "*", "@repo/eslint-config": "*",
"@repo/db": "*",
"@repo/typescript-config": "*", "@repo/typescript-config": "*",
"@turbo/gen": "^1.12.4", "@turbo/gen": "^1.12.4",
"@types/node": "^20.11.24", "@types/node": "^20.11.24",

4
packages/ui/src/.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module "*.png" {
const value: string;
export = value;
}

View File

@@ -0,0 +1,28 @@
import { BADGES } from "@repo/db";
import P1 from "./p-1.png";
import P2 from "./p-2.png";
import P3 from "./p-3.png";
import D1 from "./d-1.png";
import D2 from "./d-2.png";
import D3 from "./d-3.png";
import DAY1 from "./day-1-member.png";
const BadgeImage = {
[BADGES.P1]: P1,
[BADGES.P2]: P2,
[BADGES.P3]: P3,
[BADGES.D1]: D1,
[BADGES.D2]: D2,
[BADGES.D3]: D3,
[BADGES.DAY1]: DAY1,
};
export const Badge = ({ name }: { name: BADGES }) => {
const image = BadgeImage[name];
return (
<span className="badge">
<img src={image} />
</span>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -1,20 +0,0 @@
"use client";
import { ReactNode } from "react";
interface ButtonProps {
children: ReactNode;
className?: string;
appName: string;
}
export const Button = ({ children, className, appName }: ButtonProps) => {
return (
<button
className={className}
onClick={() => alert(`Hello from your ${appName} app!`)}
>
{children}
</button>
);
};

View File

@@ -1,27 +0,0 @@
import { type JSX } from "react";
export function Card({
className,
title,
children,
href,
}: {
className?: string;
title: string;
children: React.ReactNode;
href: string;
}): JSX.Element {
return (
<a
className={className}
href={`${href}?utm_source=create-turbo&utm_medium=basic&utm_campaign=create-turbo"`}
rel="noopener noreferrer"
target="_blank"
>
<h2>
{title} <span>-&gt;</span>
</h2>
<p>{children}</p>
</a>
);
}

View File

@@ -1,11 +0,0 @@
import { type JSX } from "react";
export function Code({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}): JSX.Element {
return <code className={className}>{children}</code>;
}

View File

@@ -1,9 +1,7 @@
import { Event, Participant } from "@repo/db"; import { Event, Participant } from "@repo/db";
export const participantCompleted = ( export const eventCompleted = (event: Event, participant?: Participant) => {
event: Event, if (!participant) return false;
participant: Participant,
) => {
if (event.finisherMoodleCourseId && !participant.finisherMoodleCurseCompleted) if (event.finisherMoodleCourseId && !participant.finisherMoodleCurseCompleted)
return false; return false;
if (event.hasPresenceEvents && !participant.attended) return false; if (event.hasPresenceEvents && !participant.attended) return false;

2
packages/ui/src/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "./Badge/Badge";
export * from "./helper/event";

View File

@@ -1,8 +1,9 @@
{ {
"extends": "@repo/typescript-config/react-library.json", "extends": "@repo/typescript-config/react-library.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist" "outDir": "dist",
"allowImportingTsExtensions": false
}, },
"include": ["src"], "include": ["src", "src/.d.ts"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }