Skip to content

Commit

Permalink
Merge pull request #31 from BirthdayResearch/nicoletan/dfc-495-ui-int…
Browse files Browse the repository at this point in the history
…egration-for-storing-email

feat(ui-ux): UI integration for storing email
  • Loading branch information
pierregee authored Mar 28, 2024
2 parents 65b5fe4 + 9c8bb63 commit bf9cfc1
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 45 deletions.
1 change: 1 addition & 0 deletions apps/web/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_SERVER_URL="http://localhost:5741/"
4 changes: 2 additions & 2 deletions apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const securityHeaders = [
`default-src 'none';` +
`base-uri 'self';` +
`child-src 'self' app.netlify.com;` +
`form-action 'none';` +
`form-action 'self';` +
`frame-ancestors 'none';` +
`img-src 'self' data:;` +
`media-src 'self';` +
Expand All @@ -15,7 +15,7 @@ const securityHeaders = [
};` +
`style-src 'self' fonts.googleapis.com 'unsafe-inline';` +
`font-src 'self' fonts.gstatic.com;` +
`connect-src 'self' ${
`connect-src 'self' ${process.env.NEXT_PUBLIC_SERVER_URL} ${
process.env.NODE_ENV === "development"
? `localhost:* 127.0.0.1:* ws://localhost:3000/_next/webpack-hmr`
: ""
Expand Down
6 changes: 4 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"start": "next start"
},
"dependencies": {
"@waveshq/walletkit-ui": "^1.3.1",
"@waveshq/standard-api-express": "^3.0.1",
"@headlessui/react": "^1.7.18",
"@reduxjs/toolkit": "^2.2.1",
"@tailwindcss/typography": "^0.5.10",
"@waveshq/standard-api-express": "^3.0.1",
"@waveshq/walletkit-ui": "^1.3.1",
"axios": "^1.6.8",
"clsx": "^2.1.0",
"connectkit": "^1.6.0",
"next13-progressbar": "^1.1.3",
Expand Down
20 changes: 12 additions & 8 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
"use client";

import { Provider } from "react-redux";
import UnlockPower from "@/app/ui/sections/UnlockPower";
import HowItWorksSection from "@/app/ui/sections/HowItWorksSection";
import MarbleOpportunitiesSection from "@/app/ui/sections/MarbleOpportunitiesSection";
import JoinTheCommunitySection from "@/app/ui/sections/JoinTheCommunitySection";
import FaqSection from "@/app/ui/sections/FaqSection";
import DFIOpportunities from "@/app/ui/sections/DFIOpportunities";
import { store } from "@/app/store/store";

export default function Page() {
return (
<div className="flex flex-col justify-center items-center w-full my-12 md:my-24 gap-y-24 md:gap-y-60">
<UnlockPower />
<MarbleOpportunitiesSection />
<HowItWorksSection />
<DFIOpportunities />
<JoinTheCommunitySection />
<FaqSection />
</div>
<Provider store={store}>
<div className="flex flex-col justify-center items-center w-full my-12 md:my-24 gap-y-24 md:gap-y-60">
<UnlockPower />
<MarbleOpportunitiesSection />
<HowItWorksSection />
<DFIOpportunities />
<JoinTheCommunitySection />
<FaqSection />
</div>
</Provider>
);
}
23 changes: 23 additions & 0 deletions apps/web/src/app/store/marbleFiApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { CreateUserI } from "@/app/types/user";
import process from "process";

// eslint-disable-next-line import/prefer-default-export
export const marbleFiApi = createApi({
reducerPath: "marbleFiApi",
baseQuery: fetchBaseQuery({ baseUrl: process.env.NEXT_PUBLIC_SERVER_URL }),
endpoints: (builder) => ({
createUser: builder.mutation<any, CreateUserI>({
query: ({ email, status }) => ({
url: "/user",
body: {
email: email,
status: status,
},
method: "POST",
}),
}),
}),
});

export const { useCreateUserMutation } = marbleFiApi;
6 changes: 6 additions & 0 deletions apps/web/src/app/store/reducers/rootReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { marbleFiApi } from "@/app/store/marbleFiApi";

const rootReducer = marbleFiApi.reducer;
export type RootState = ReturnType<typeof rootReducer>;

export default rootReducer;
15 changes: 15 additions & 0 deletions apps/web/src/app/store/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { configureStore } from "@reduxjs/toolkit";
import { marbleFiApi } from "@/app/store/marbleFiApi";

export const store = configureStore({
reducer: {
[marbleFiApi.reducerPath]: marbleFiApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(marbleFiApi.middleware),
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
9 changes: 9 additions & 0 deletions apps/web/src/app/types/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const enum SubscriptionStatus {
ACTIVE = "ACTIVE",
INACTIVE = "INACTIVE",
}

export interface CreateUserI {
email: string;
status?: SubscriptionStatus;
}
15 changes: 13 additions & 2 deletions apps/web/src/app/ui/components/CTAButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,34 @@ export function CTAButton({
testID,
customStyle,
customTextStyle = "text-light-00",
onClick,
isDisabled = false,
}: {
text: string;
customStyle?: string;
customTextStyle?: string;
testID: string;
onClick?: (e: any) => void;
isDisabled?: boolean;
}) {
return (
<button
disabled={isDisabled}
data-testid={`cta-button-${testID}`}
className={clsx(
"primary-btn-ui px-9 py-5 md:py-4",
"hover:bg-opacity-60",
customTextStyle,
"disabled:opacity-30",
customStyle ?? "w-fit",
)}
onClick={onClick}
>
<span className="active:text-opacity-60 text-sm font-bold text-light-1000">
<span
className={clsx(
"active:text-opacity-60 text-sm font-bold text-light-1000",
customTextStyle,
)}
>
{text}
</span>
</button>
Expand Down
115 changes: 87 additions & 28 deletions apps/web/src/app/ui/components/EmailInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { IoIosCloseCircle } from "react-icons/io";
import { HiOutlineMail } from "react-icons/hi";
import clsx from "clsx";
import { CTAButton } from "@/app/ui/components/CTAButton";
import { SubscriptionStatus } from "@/app/types/user";
import { useCreateUserMutation } from "@/app/store/marbleFiApi";
import { useEffect, useState } from "react";
import { FaCheck } from "react-icons/fa6";

export default function EmailInput({
value,
Expand All @@ -13,45 +18,99 @@ export default function EmailInput({
placeholder?: string;
customStyle?: string;
}) {
const [createUser] = useCreateUserMutation();
const [errorMsg, setErrorMsg] = useState<string>("");
const [success, setSuccess] = useState<boolean>(false);
const isValidEmail = (email: string) => {
// Regular expression for email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const handleSubmit = async (
e: React.FormEvent<HTMLFormElement>,
email: string,
status?: SubscriptionStatus,
) => {
e.preventDefault();
try {
const user = {
email: email,
status: status,
};
const data = await createUser(user);
// @ts-ignore
if (data?.error) {
// @ts-ignore
setErrorMsg(data.error.data.message);
setSuccess(false);
} else {
setErrorMsg("");
setSuccess(true);
}
} catch (error) {
console.error("Error creating user:", error);
}
};
useEffect(() => {
setSuccess(false);
setErrorMsg("");
}, [value]);
return (
<div
className={clsx(
"border border-light-1000/10 rounded-[32px] flex relative w-full md:w-[472px]",
value && !isValidEmail(value) ? "bg-red" : "input-gradient-1",
)}
>
<form
<div>
<div
className={clsx(
"relative w-full md:w-[472px] py-4 px-7 flex items-center bg-light-00 rounded-[32px]",
customStyle,
"border border-light-1000/10 rounded-[32px] flex relative w-full md:w-[472px]",
value && !isValidEmail(value) ? "bg-red" : "input-gradient-1",
)}
>
<HiOutlineMail className="mr-3" size={20} />
<input
<form
className={clsx(
"mr-6 w-full bg-light-00 caret-brand-100",
"placeholder:text-light-1000 focus:outline-none",
"relative w-full md:w-[472px] py-4 px-7 flex items-center bg-light-00 rounded-[32px]",
customStyle,
)}
type="text"
placeholder={placeholder}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{value !== "" && (
<button
type="button"
className="absolute right-7 rounded-full"
onClick={() => setValue("")}
>
<IoIosCloseCircle size={16} className="opacity-70 text-dark-00" />
</button>
)}
</form>
>
<HiOutlineMail className="mr-3" size={20} />
<input
className={clsx(
"mr-6 w-full bg-light-00 caret-brand-100",
"placeholder:text-light-1000 focus:outline-none",
)}
type="text"
placeholder={placeholder}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<div className="flex absolute right-4">
{value !== "" && !success && (
<button
type="button"
className="rounded-full mr-3"
onClick={() => setValue("")}
>
<IoIosCloseCircle
size={16}
className="opacity-70 text-dark-00"
/>
</button>
)}
{success ? (
<FaCheck className="text-valid w-5 h-5" />
) : (
<CTAButton
text="Submit"
testID="join-community-submit-btn"
onClick={(e) => handleSubmit(e, value)}
isDisabled={!isValidEmail(value) || value == ""}
customStyle="!py-2 !px-5"
customTextStyle="text-[#B2B2B2] font-semibold text-xs"
/>
)}
</div>
</form>
</div>
{errorMsg && (
<div className="text-left mt-2 ml-2 text-sm text-red">{errorMsg}</div>
)}
</div>
);
}
Loading

0 comments on commit bf9cfc1

Please sign in to comment.