Skip to content

Feat/os project boilerpalte #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
12aafb5
add projets and os projects route
tigawanna Dec 13, 2024
ea078a9
remove test code
tigawanna Dec 13, 2024
82b4bba
feat: add ProjectCard component and integrate faker for project data …
tigawanna Dec 13, 2024
255bd2e
fix filtering
tigawanna Dec 13, 2024
b0f9b25
Merge branch 'main' into feat/os-projects-route
tigawanna Dec 15, 2024
50901a0
add scrill restoration
tigawanna Dec 15, 2024
1ffa8c1
feat: add scroll restoration
tigawanna Dec 15, 2024
e9a7ae5
feat: projects list page and one project page
tigawanna Dec 15, 2024
afb646f
feat: update project languages structure to include color property
tigawanna Dec 15, 2024
b60189e
feat: update RouterPendingComponent to display loading image
tigawanna Dec 16, 2024
28651ea
feat: add Submit Project page and routing
tigawanna Dec 16, 2024
32188a9
feat: refactor project properties to use 'title' instead of 'name' an…
tigawanna Dec 16, 2024
8b5b5b4
feat: update project form to include monetization toggle and change p…
tigawanna Dec 16, 2024
a324a31
feat: update ProjectsList component and filterProjects function to us…
tigawanna Dec 17, 2024
a148708
feat: enhance project form to include monetization options and curren…
tigawanna Dec 17, 2024
8bee838
feat: add MonetizationFields component for project compensation options
tigawanna Dec 18, 2024
0e6b97d
feat: integrate MonetizationFields component and update compensation …
tigawanna Dec 18, 2024
e7a8268
feat: add utility functions for locale and currency retrieval
tigawanna Dec 18, 2024
7d36e6c
feat: refactor project form to include ProjectTypeFields component an…
tigawanna Dec 18, 2024
5bfe718
feat: improve layout of project form and submission page for better a…
tigawanna Dec 18, 2024
c0aaaf2
feat: add InviteUsersField component for inviting collaborators in pr…
tigawanna Dec 18, 2024
b6f3261
feat: add RepositoryFields component for GitHub repo link input and e…
tigawanna Dec 19, 2024
5d076af
feat: update @hookform/resolvers to version 3.9.1 and enhance form er…
tigawanna Dec 20, 2024
0cb1a5c
feat: implement RHFInput and RHFErrorWrapper components, enhance proj…
tigawanna Dec 20, 2024
867ee1c
feat: implement RHFTextInput component for improved input handling in…
tigawanna Dec 20, 2024
0bc0554
feat: refactor project form components to use RHFTextInput and add Fo…
tigawanna Dec 20, 2024
6183db7
feat: update project form validation to require a minimum title lengt…
tigawanna Dec 20, 2024
6e281d7
feat: enhance BaseProjectsForm to accept a dynamic Zod schema for val…
tigawanna Dec 20, 2024
11946a3
feat: update onSubmit function in BaseProjectsForm to include mutatio…
tigawanna Dec 20, 2024
61f8c16
fix: add validation and error otputs to all fields
tigawanna Dec 21, 2024
57bcca8
feat: add afterSave callback to onSubmit in BaseProjectsForm; remove …
tigawanna Dec 23, 2024
0ba6af5
feat: add settings route and component; update dashboard project link…
tigawanna Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/TANSTACK-QUERY.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@

run `p/mpm run page users` to scafold a page with these best practices


## handling forms

We'll use [react-hook-form](https://react-hook-form.com/) to manage our forms and [zod](https://zod.dev/) to validate our data.
[quick tutorial on react hhok form + zod](https://www.youtube.com/watch?v=cc_xmawJ8Kg)

then use the useMutation hook
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test-ct": "playwright test -c playwright-ct.config.ts"
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
Expand Down Expand Up @@ -70,6 +70,7 @@
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@faker-js/faker": "^9.3.0",
"@playwright/experimental-ct-react": "^1.49.0",
"@playwright/test": "^1.49.0",
"@tanstack/eslint-plugin-query": "^5.61.6",
Expand Down
3,215 changes: 1,602 additions & 1,613 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/components/navigation/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const dashboard_routes = [
},
{
name: "OS Projects",
href: "/dashboard/os-projects",
href: "/dashboard/osprojects",
icon: <Layers />,
},
{
Expand Down
1 change: 1 addition & 0 deletions src/components/pagination/ReactresponsivePagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function ListPagination({ total_pages }: ListingsPaginationProps) {
from: "__root__",
});
const navigate = useNavigate({
// @ts-expect-error : search param type is possibly undefined but it will exist when we need it
from: location.pathname,
});

Expand Down
93 changes: 93 additions & 0 deletions src/data/currency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
export const dummyCurremcies = [
{
code: "USD",
name: "United States Dollar",
symbol: "$",
},
{
code: "EUR",
name: "Euro",
symbol: "€",
},
{
code: "GBP",
name: "British Pound Sterling",
symbol: "£",
},
{
code: "JPY",
name: "Japanese Yen",
symbol: "¥",
},
{
code: "CNY",
name: "Chinese Yuan",
symbol: "¥",
},
{
code: "INR",
name: "Indian Rupee",
symbol: "₹",
},
{
code: "RUB",
name: "Russian Ruble",
symbol: "₽",
},
{
code: "KRW",
name: "South Korean Won",
symbol: "₩",
},
{
code: "BRL",
name: "Brazilian Real",
symbol: "R$",
},
{
code: "MXN",
name: "Mexican Peso",
symbol: "$",
},
{
code: "CHF",
name: "Swiss Franc",
symbol: "CHF",
},
{
code: "AUD",
name: "Australian Dollar",
symbol: "$",
},
{
code: "CAD",
name: "Canadian Dollar",
symbol: "$",
},
{
code: "KES",
name: "Kenyan Shilling",
symbol: "KSh",
},
{
code: "TZS",
name: "Tanzanian Shilling",
symbol: "TSh",
},
{
code: "NGN",
name: "Nigerian Naira",
symbol: "₦",
},
{
code: "ZAR",
name: "South African Rand",
symbol: "R",
},
{
code: "ETB",
name: "Ethiopian Birr",
symbol: "ETB",
},
];

41 changes: 29 additions & 12 deletions src/hooks/use-page-searchquery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,43 @@ import { useTransition, useState, useEffect } from "react";
import { useDebouncedValue } from "./use-debouncer";
import { ValidRoutes } from "@/lib/tanstack/router/router-types";

/**
* Use this hook to generate a debounced search query that can be used in a input search field.
* @param path the path of the route that the search query will be applied to
* @returns an object containing the debounced search query, a boolean indicating if the debouncer is running, the current keyword and a function to update the keyword
* Use this hook in a page component that declares a sq search param in the route
*/
/**
* Use this hook to generate a debounced search query that can be used in a input search field.
* @param path the path of the route that the search query will be applied to
* @returns an object containing the debounced search query, a boolean indicating if the debouncer is running, the current keyword and a function to update the keyword
* Use this hook in a page component that declares a sq search param in the route
*/
export function usePageSearchQuery(path: ValidRoutes) {
// @ts-expect-error : search parm below wwill exist when the compnent is usesd
const { sq, page } = useSearch({ from:`${path}/` });
const navigate = useNavigate({ from:path });
const { sq, page } = useSearch({ from: `${path}/` });
const navigate = useNavigate({ from: path });
const [_, startTransition] = useTransition();

const [keyword, setKeyword] = useState(sq ?? "");
const { debouncedValue, isDebouncing } = useDebouncedValue(keyword, 2000);
// reset the page to 1 when the keyword changes
useEffect(() => {
startTransition(() => {
navigate({
search: {
page: 1,
},
});
});
}, [keyword,navigate]);
useEffect(() => {
if (sq !== debouncedValue) {
startTransition(() => {
navigate({
search: {
page,
page: 1,
sq: debouncedValue,
},
});
});

}
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedValue, navigate, page]);
function updatePage(page: number) {
startTransition(() => {
Expand All @@ -39,7 +49,14 @@ export function usePageSearchQuery(path: ValidRoutes) {
sq: debouncedValue,
},
});
})
});
}
return { debouncedValue, isDebouncing, keyword, setKeyword,page, updatePage };
return {
debouncedValue,
isDebouncing,
keyword,
setKeyword,
page,
updatePage,
};
}
62 changes: 62 additions & 0 deletions src/lib/react-oook-form/RHFInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { getNestedProperty, PossibleNestedUnions } from "@/utils/object";
import { Controller, FieldError, UseFormReturn } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RHFErrorWrapper } from "./RHFWrappers";
import { twMerge } from "tailwind-merge";
type ExtractFormType<T> =
T extends UseFormReturn<infer U, any, undefined> ? U : never;

interface RHFTextInputProps<T extends UseFormReturn<any, any, undefined>> {
fieldKey: PossibleNestedUnions<ExtractFormType<T>>;
form: T;
placeholder: string;
label?: string;
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
containerClassName?: string;
labelClassName?: string;
}

export function RHFTextInput<T extends UseFormReturn<any, any, undefined>>({
form,
fieldKey,
label,
placeholder,
inputProps,
containerClassName,
labelClassName,
}: RHFTextInputProps<T>) {
const { control, register } = form;
const fieldError = getNestedProperty<FieldError>(
form?.formState?.errors,
fieldKey,
);
return (
<div className={twMerge("flex flex-col gap-1 w-full", containerClassName)}>
{label && (
<Label className={twMerge("inline-flex gap-1", labelClassName)}>
{label}
</Label>
)}
<Controller
name={fieldKey}
control={control}
render={({ field }) => {
return (
<Input
{...inputProps}
{...field}
className={twMerge(
`w-full rounded border border-[#737776CC] bg-transparent ${fieldError?.message ? "border-4 border-error" : ""}`,
inputProps?.className,
)}
placeholder={placeholder}
{...register(fieldKey, { valueAsNumber: inputProps?.type === "number" })}
/>
);
}}
/>
<RHFErrorWrapper form={form} fieldKey={fieldKey} />
</div>
);
}
44 changes: 44 additions & 0 deletions src/lib/react-oook-form/RHFWrappers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getNestedProperty, PossibleNestedUnions } from "@/utils/object";
import { FieldError, UseFormReturn } from "react-hook-form";

type ExtractFormType<T> =
T extends UseFormReturn<infer U, any, undefined> ? U : never;


interface RHFErrorWrapperProps<T extends UseFormReturn<any, any, undefined>> {
fieldKey: PossibleNestedUnions<ExtractFormType<T>>;
form: T;
}

export function RHFErrorWrapper<T extends UseFormReturn<any, any, undefined>>({
fieldKey,
form,
}: RHFErrorWrapperProps<T>) {
const nestedKeys = fieldKey.split(".");
if (nestedKeys.length > 1) {
const errorObject = getNestedProperty<FieldError>(form?.formState?.errors, fieldKey);
return (
<div className="text-sm italic text-error-content">
<div className="text-sm italic text-error-content">
{errorObject?.message}
</div>
</div>
);
}
const errorObject = form?.formState?.errors?.[fieldKey];
if (!errorObject) return null;
if (typeof errorObject?.message === "string") {
return (
<div className="text-sm italic text-error-content">
{errorObject?.message}
</div>
);
}
if (typeof errorObject === "object") {
return (
<div className="text-sm italic text-error-content">
{Object.values(errorObject).join(", \n ")}
</div>
);
}
}
8 changes: 4 additions & 4 deletions src/lib/tanstack/router/RouterPendingComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ interface RouterPendingComponentProps {}

export function RouterPendingComponent({}: RouterPendingComponentProps) {
return (
<div className="flex min-h-screen w-full flex-col items-center justify-center gap-2">
<div className="skeleton h-[40vh] w-full rounded-lg bg-base-300" />
<div className="justify-center' flex w-full flex-col items-center md:flex-row">
<div className="flex min-h-screen w-full flex-col items-center justify-center gap-2">
{/* <div className="justify-center' flex w-full flex-col items-center md:flex-row">
<div className="skeleton h-[40vh] w-full rounded-lg bg-base-300" />
<div className="skeleton h-[40vh] w-full rounded-lg bg-base-300" />
</div>
</div> */}
<img src="/colabs.png" alt="Loading..." />
</div>
);
}
Loading
Loading