diff --git a/package.json b/package.json
index 7e91ddd465..18bd37d06f 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,9 @@
},
"dependencies": {
"@better-giving/assets": "1.0.18",
+ "@better-giving/fundraiser": "1.0.0-rc.5",
"@better-giving/registration": "1.0.24",
+ "@better-giving/types": "1.0.0-rc.2",
"@gsap/react": "2.1.1",
"@headlessui/react": "2.1.0",
"@hookform/error-message": "2.0.1",
diff --git a/src/pages/Admin/Charity/Funds/Funds.tsx b/src/pages/Admin/Charity/Funds/Funds.tsx
index 8ff4afb96c..66fcbfe32f 100644
--- a/src/pages/Admin/Charity/Funds/Funds.tsx
+++ b/src/pages/Admin/Charity/Funds/Funds.tsx
@@ -1,3 +1,4 @@
+import type { FundItem as TFundItem } from "@better-giving/fundraiser";
import ContentLoader from "components/ContentLoader";
import Image from "components/Image";
import QueryLoader from "components/QueryLoader";
@@ -5,7 +6,6 @@ import { appRoutes } from "constants/routes";
import { useAuthenticatedUser } from "contexts/Auth";
import { Link } from "react-router-dom";
import { useFundsEndowMemberOfQuery } from "services/aws/aws";
-import type { Fund } from "types/aws";
import { useAdminContext } from "../../Context";
export function Funds() {
@@ -41,7 +41,7 @@ export function Funds() {
);
}
-const FundItem = (props: Fund.Card) => {
+const FundItem = (props: TFundItem) => {
const user = useAuthenticatedUser();
const isActive = new Date().toISOString() <= props.expiration && props.active;
const isEditor = user.funds.includes(props.id);
diff --git a/src/pages/Funds/Cards/Card.tsx b/src/pages/Funds/Cards/Card.tsx
index 186c3f7240..1fcadfe37c 100644
--- a/src/pages/Funds/Cards/Card.tsx
+++ b/src/pages/Funds/Cards/Card.tsx
@@ -1,9 +1,9 @@
+import type { FundItem } from "@better-giving/fundraiser";
import flying_character from "assets/images/flying-character.png";
import Image from "components/Image";
import VerifiedIcon from "components/VerifiedIcon";
import { appRoutes } from "constants/routes";
import { Link } from "react-router-dom";
-import type { Fund } from "types/aws";
import { Progress } from "./Progress";
export default function Card({
@@ -14,7 +14,7 @@ export default function Card({
verified,
donation_total_usd,
target,
-}: Fund.Card) {
+}: FundItem) {
return (
+ props: Pick
) {
if (props.target === "0") {
if (!props.donation_total_usd) return;
diff --git a/src/pages/Funds/CreateFund/CreateFund.tsx b/src/pages/Funds/CreateFund/CreateFund.tsx
index b0d840b7f1..3b03d6362d 100644
--- a/src/pages/Funds/CreateFund/CreateFund.tsx
+++ b/src/pages/Funds/CreateFund/CreateFund.tsx
@@ -1,3 +1,4 @@
+import type { NewFund } from "@better-giving/fundraiser/schema";
import { valibotResolver } from "@hookform/resolvers/valibot";
import { ControlledImgEditor as ImgEditor } from "components/ImgEditor";
import Prompt from "components/Prompt";
@@ -19,7 +20,6 @@ import { type SubmitHandler, useController, useForm } from "react-hook-form";
import { Link } from "react-router-dom";
import { useLazyProfileQuery } from "services/aws/aws";
import { useCreateFundMutation } from "services/aws/funds";
-import type { Fund } from "types/aws";
import { GoalSelector, MAX_SIZE_IN_BYTES, VALID_MIME_TYPES } from "../common";
import { EndowmentSelector } from "./EndowmentSelector";
import { type FV, schema } from "./schema";
@@ -88,7 +88,7 @@ export default withAuth(function CreateFund() {
showModal(Prompt, { type: "loading", children: "Creating fund..." });
- const fund: Fund.New = {
+ const fund: NewFund = {
name: fv.name,
description: fv.description,
banner: getFullURL(uploadBaseUrl, banner.file.name),
diff --git a/src/pages/Funds/EditFund/Form.tsx b/src/pages/Funds/EditFund/Form.tsx
index a74ec3dcb4..16fb14ce1d 100644
--- a/src/pages/Funds/EditFund/Form.tsx
+++ b/src/pages/Funds/EditFund/Form.tsx
@@ -1,3 +1,5 @@
+import type { SingleFund } from "@better-giving/fundraiser";
+import type { FundUpdate } from "@better-giving/fundraiser/schema";
import {
ControlledImgEditor as ImgEditor,
type ImgLink,
@@ -9,13 +11,15 @@ import { useModalContext } from "contexts/ModalContext";
import { getFullURL, uploadFiles } from "helpers/uploadFiles";
import type { SubmitHandler } from "react-hook-form";
import { useCloseFundMutation, useEditFundMutation } from "services/aws/funds";
-import type { Fund } from "types/aws";
import { GoalSelector, MAX_SIZE_IN_BYTES, VALID_MIME_TYPES } from "../common";
import { FeatureBanner } from "./FeatureBanner";
-import type { FV } from "./types";
+import type { FV } from "./schema";
import { useRhf } from "./useRhf";
-export function Form({ classes = "", ...props }: Fund & { classes?: string }) {
+export function Form({
+ classes = "",
+ ...props
+}: SingleFund & { classes?: string }) {
const { showModal } = useModalContext();
const { handleError } = useErrorContext();
const rhf = useRhf(props);
@@ -24,8 +28,7 @@ export function Form({ classes = "", ...props }: Fund & { classes?: string }) {
const [closeFund, { isLoading: isClosingFund }] = useCloseFundMutation();
const onSubmit: SubmitHandler = async ({
- targetType,
- fixedTarget,
+ target,
logo,
banner,
...fv
@@ -40,15 +43,15 @@ export function Form({ classes = "", ...props }: Fund & { classes?: string }) {
});
/// BUILD UPDATE ///
- const update: Fund.Update = {};
+ const update: FundUpdate = {};
- if (rhf.dirtyFields.targetType || rhf.dirtyFields.fixedTarget) {
+ if (rhf.dirtyFields.target) {
update.target =
- targetType === "none"
+ target.type === "none"
? "0"
- : targetType === "smart"
+ : target.type === "smart"
? "smart"
- : (fixedTarget as `${number}`);
+ : target.value;
}
if (rhf.dirtyFields.banner) update.banner = bannerUrl;
@@ -161,11 +164,11 @@ export function Form({ classes = "", ...props }: Fund & { classes?: string }) {
/>
{rhf.targetType.value === "fixed" && (
)}
diff --git a/src/pages/Funds/EditFund/schema.ts b/src/pages/Funds/EditFund/schema.ts
new file mode 100644
index 0000000000..df00985a79
--- /dev/null
+++ b/src/pages/Funds/EditFund/schema.ts
@@ -0,0 +1,27 @@
+import { fileObject } from "@better-giving/types";
+import * as v from "valibot";
+import { MAX_SIZE_IN_BYTES, VALID_MIME_TYPES, target } from "../common";
+
+const str = v.pipe(v.string(), v.trim());
+
+export const imgLink = v.object({
+ file: v.optional(
+ v.pipe(
+ v.file("required"),
+ v.mimeType(VALID_MIME_TYPES, "invalid type"),
+ v.maxSize(MAX_SIZE_IN_BYTES, "exceeds size limit")
+ )
+ ),
+ preview: v.pipe(str, v.url()),
+ ...fileObject.entries,
+});
+
+export const schema = v.object({
+ name: v.pipe(str, v.nonEmpty("required")),
+ description: v.pipe(str, v.nonEmpty("required")),
+ target,
+ banner: imgLink,
+ logo: imgLink,
+});
+
+export type FV = v.InferOutput;
diff --git a/src/pages/Funds/EditFund/types.ts b/src/pages/Funds/EditFund/types.ts
deleted file mode 100644
index 059ba933fa..0000000000
--- a/src/pages/Funds/EditFund/types.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import type { ImgLink } from "components/ImgEditor";
-import type { Fund } from "types/aws";
-import type { TargetType } from "../common";
-
-export interface FV extends Pick {
- targetType: TargetType;
- fixedTarget: string;
- banner: ImgLink;
- logo: ImgLink;
-}
diff --git a/src/pages/Funds/EditFund/useRhf.ts b/src/pages/Funds/EditFund/useRhf.ts
index e68887e2b6..37d8063422 100644
--- a/src/pages/Funds/EditFund/useRhf.ts
+++ b/src/pages/Funds/EditFund/useRhf.ts
@@ -1,46 +1,9 @@
-import { yupResolver } from "@hookform/resolvers/yup";
-import type { ImgLink } from "components/ImgEditor";
+import type { SingleFund } from "@better-giving/fundraiser";
+import { valibotResolver } from "@hookform/resolvers/valibot";
import { useController, useForm } from "react-hook-form";
-import { genFileSchema } from "schemas/file";
-import { schema as schemaFn, stringNumber } from "schemas/shape";
-import type { Fund } from "types/aws";
-import { string } from "yup";
-import {
- MAX_SIZE_IN_BYTES,
- type TargetType,
- VALID_MIME_TYPES,
-} from "../common";
-import type { FV } from "./types";
+import { type FV, schema } from "./schema";
-const fileObj = schemaFn({
- file: genFileSchema(MAX_SIZE_IN_BYTES, VALID_MIME_TYPES),
-});
-
-const targetTypeKey: keyof FV = "targetType";
-const schema = schemaFn({
- name: string().required("required"),
- description: string().required("required"),
- fixedTarget: stringNumber(
- (str) => {
- return str.when(targetTypeKey, (values, schema) => {
- const [type] = values as [TargetType];
- return type === "fixed" ? schema.required("required") : schema;
- });
- },
- (n) => {
- return n.when(targetTypeKey, (values, schema) => {
- const [type] = values as [TargetType];
- return type === "fixed"
- ? schema.positive("must be greater than 0")
- : schema;
- });
- }
- ),
- logo: fileObj,
- banner: fileObj,
-});
-
-export function useRhf(init: Fund) {
+export function useRhf(init: SingleFund) {
const {
register,
handleSubmit,
@@ -49,17 +12,16 @@ export function useRhf(init: Fund) {
resetField,
formState: { isSubmitting, errors, isDirty, dirtyFields },
} = useForm({
- resolver: yupResolver(schema),
+ resolver: valibotResolver(schema),
values: {
name: init.name,
description: init.description,
- targetType:
+ target:
init.target === "0"
- ? "none"
+ ? { type: "none" }
: init.target === "smart"
- ? "smart"
- : "fixed",
- fixedTarget: init.target === "smart" ? "" : init.target,
+ ? { type: "smart" }
+ : { type: "fixed", value: init.target },
logo: { name: "", preview: init.logo, publicUrl: init.logo },
banner: { name: "", preview: init.banner, publicUrl: init.banner },
},
@@ -67,7 +29,7 @@ export function useRhf(init: Fund) {
const { field: targetType } = useController({
control,
- name: "targetType",
+ name: "target.type",
});
const { field: logo } = useController({ control, name: "logo" });
diff --git a/src/pages/Funds/Fund/FundContext.ts b/src/pages/Funds/Fund/FundContext.ts
index 89a2135242..8d0d74ee09 100644
--- a/src/pages/Funds/Fund/FundContext.ts
+++ b/src/pages/Funds/Fund/FundContext.ts
@@ -1,10 +1,10 @@
+import type { SingleFund } from "@better-giving/fundraiser";
import { isEmpty } from "helpers";
import { createContext, useContext } from "react";
-import type { Fund } from "types/aws";
-export const FundContext = createContext({} as Fund);
+export const FundContext = createContext({} as SingleFund);
-export const useFundContext = (): Fund => {
+export const useFundContext = (): SingleFund => {
const val = useContext(FundContext);
if (isEmpty(Object.entries(val))) {
diff --git a/src/services/aws/aws.ts b/src/services/aws/aws.ts
index 4c49cf2b85..e880001b26 100644
--- a/src/services/aws/aws.ts
+++ b/src/services/aws/aws.ts
@@ -1,3 +1,4 @@
+import type { FundItem } from "@better-giving/fundraiser";
import type {
Application,
Page,
@@ -19,7 +20,6 @@ import type {
EndowmentCard,
EndowmentOption,
EndowmentsQueryParams,
- Fund,
} from "types/aws";
import { version as v } from "../helpers";
import type { EndowmentUpdate, IdOrSlug } from "../types";
@@ -158,7 +158,7 @@ export const aws = createApi({
}
},
}),
- fundsEndowMemberOf: builder.query({
+ fundsEndowMemberOf: builder.query({
providesTags: ["endowment"],
query: ({ endowId }) => {
return {
diff --git a/src/services/aws/funds.ts b/src/services/aws/funds.ts
index b32463b0d5..63733b7397 100644
--- a/src/services/aws/funds.ts
+++ b/src/services/aws/funds.ts
@@ -1,11 +1,16 @@
+import type { FundsPage, SingleFund } from "@better-giving/fundraiser";
+import type {
+ FundUpdate,
+ FundsParams,
+ NewFund,
+} from "@better-giving/fundraiser/schema";
import { TEMP_JWT } from "constants/auth";
-import type { Fund } from "types/aws";
import { version as v } from "../helpers";
import { aws } from "./aws";
export const funds = aws.injectEndpoints({
endpoints: (builder) => ({
- createFund: builder.mutation<{ id: string }, Fund.New>({
+ createFund: builder.mutation<{ id: string }, NewFund>({
invalidatesTags: ["funds"],
query: (payload) => {
return {
@@ -16,7 +21,7 @@ export const funds = aws.injectEndpoints({
};
},
}),
- editFund: builder.mutation({
+ editFund: builder.mutation({
invalidatesTags: ["funds", "fund"],
query: ({ id, ...payload }) => {
return {
@@ -37,7 +42,7 @@ export const funds = aws.injectEndpoints({
};
},
}),
- funds: builder.query({
+ funds: builder.query({
providesTags: ["funds"],
query: (params) => {
return {
@@ -46,7 +51,7 @@ export const funds = aws.injectEndpoints({
};
},
}),
- fund: builder.query({
+ fund: builder.query({
providesTags: ["fund"],
query: (fundId) => `${v(1)}/funds/${fundId}`,
}),
diff --git a/src/types/aws/ap/index.ts b/src/types/aws/ap/index.ts
index 63ee1ea702..45cf3405f2 100644
--- a/src/types/aws/ap/index.ts
+++ b/src/types/aws/ap/index.ts
@@ -1,6 +1,5 @@
import type { Except } from "type-fest";
import type { PartialExcept } from "types/utils";
-import type { Environment } from "vitest";
import type { DonateMethodId, UNSDG_NUMS } from "../../lists";
export type Milestone = {
@@ -234,83 +233,3 @@ export type UserAttributes = {
};
export type UserUpdate = Partial;
-
-export interface Fund {
- /** uuidv4 */
- id: string;
- env: Environment;
- name: string;
- description: string;
- banner: string;
- logo: string;
- members: Pick[];
- featured: boolean;
- active: boolean;
- settings: {
- allowBgTip: boolean;
- };
- /** iso */
- expiration?: string;
- verified: boolean;
- donation_total_usd: number;
- /** "0": no target */
- target: "smart" | `${number}`;
- /** endowIds that allows this fundraiser on their profile */
- approvers: number[];
-}
-export namespace Fund {
- export interface New
- extends Pick<
- Fund,
- | "name"
- | "description"
- | "banner"
- | "logo"
- | "featured"
- | "settings"
- | "expiration"
- | "target"
- > {
- /** endowment ids */
- members: number[];
- }
-
- export interface Update
- extends Partial<
- Pick<
- Fund,
- "name" | "description" | "banner" | "logo" | "featured" | "target"
- >
- > {}
-
- export interface Card
- extends Pick<
- Fund,
- | "id"
- | "name"
- | "description"
- | "env"
- | "logo"
- | "featured"
- | "active"
- | "verified"
- | "donation_total_usd"
- | "members"
- | "target"
- | "approvers"
- > {
- /** iso | "9999-12-31T23:59:59.000Z" year 9999 */
- expiration: string;
- }
-
- export interface CardsPage {
- items: Card[];
- page: number;
- numPages: number;
- }
-
- export interface CardsQueryParams {
- query?: string;
- page?: number;
- }
-}
diff --git a/yarn.lock b/yarn.lock
index a7b866cb6c..776b292d36 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -822,6 +822,18 @@ __metadata:
languageName: node
linkType: hard
+"@better-giving/fundraiser@npm:1.0.0-rc.5":
+ version: 1.0.0-rc.5
+ resolution: "@better-giving/fundraiser@npm:1.0.0-rc.5"
+ dependencies:
+ "@better-giving/types": "npm:1.0.0-rc.2"
+ valibot: "npm:0.42.0"
+ peerDependencies:
+ valibot: 0.42.0
+ checksum: 10/1c0c631b618790082cd84516fd3e039bd204847876d31f80eb0c47f44f83e6baba304e52cd7c5326412af7f59aa6e65e8a06d8889b3b4eb1287227dc44300ea7
+ languageName: node
+ linkType: hard
+
"@better-giving/registration@npm:1.0.24":
version: 1.0.24
resolution: "@better-giving/registration@npm:1.0.24"
@@ -831,6 +843,15 @@ __metadata:
languageName: node
linkType: hard
+"@better-giving/types@npm:1.0.0-rc.2":
+ version: 1.0.0-rc.2
+ resolution: "@better-giving/types@npm:1.0.0-rc.2"
+ peerDependencies:
+ valibot: 0.42.0
+ checksum: 10/a4b46d4fb5fce7d21982f636ff922f9a335ab754030142ef731944b8a4e682e0b3c19a133dc9d9e05ad8dbb32e513dcb66e59007393ce7ead474ed8559d36174
+ languageName: node
+ linkType: hard
+
"@biomejs/biome@npm:1.8.1":
version: 1.8.1
resolution: "@biomejs/biome@npm:1.8.1"
@@ -3426,7 +3447,9 @@ __metadata:
resolution: "angelprotocol-web-app@workspace:."
dependencies:
"@better-giving/assets": "npm:1.0.18"
+ "@better-giving/fundraiser": "npm:1.0.0-rc.5"
"@better-giving/registration": "npm:1.0.24"
+ "@better-giving/types": "npm:1.0.0-rc.2"
"@biomejs/biome": "npm:1.8.1"
"@gsap/react": "npm:2.1.1"
"@headlessui/react": "npm:2.1.0"