Skip to content
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

Feat/social share modal #239

Closed
wants to merge 83 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
641f5f0
feat: admin panel tracks CRUD operations
Markkos89 Feb 4, 2024
05cabf7
feat: admin panel tracks CRUD operations
Markkos89 Feb 4, 2024
20637f2
feat: fixed page export default error
Markkos89 Feb 4, 2024
a2907f8
feat: update checkpoint
Markkos89 Feb 6, 2024
1a4daa3
Merge branch 'staging' of https://github.com/Developer-DAO/academy-tu…
Markkos89 Feb 19, 2024
0d567e2
feat: created new project app for admin panel
Markkos89 Feb 19, 2024
2148c6b
Merge branch 'staging' of https://github.com/Developer-DAO/academy-tu…
Markkos89 Feb 19, 2024
4da1975
feat: changes and using private route with small security for now
Markkos89 Feb 19, 2024
53eec09
chore: update turborepo version
Markkos89 Mar 28, 2024
ee11755
Merge branch 'staging' of https://github.com/Developer-DAO/academy-tu…
Markkos89 Mar 28, 2024
07e5275
feat: this is a more estable version wip
Markkos89 Mar 28, 2024
2e26980
feat: create new track form. needs refactor
Markkos89 Mar 31, 2024
3d942f2
feat: create and edit track working
Markkos89 Mar 31, 2024
8929817
feat: clean unnused dependecy
Markkos89 Mar 31, 2024
68eeef4
feat: small enhancements and files location
Markkos89 Mar 31, 2024
873770e
feat: save checkpoint tags forms and pages
Markkos89 Mar 31, 2024
4c2680a
feat: lessons, tracks and tags list, edit and create
Markkos89 Mar 31, 2024
9e0e4e6
feat: new Authors model on schema.prisma file
Markkos89 Mar 31, 2024
dfdc8e0
feat: new lessons page with table and new nav options
Markkos89 Mar 31, 2024
df44a59
feat: little pages title adjustment
Markkos89 Apr 1, 2024
c38e53c
Merge pull request #222 from Developer-DAO/feat/admin-panel-rhf
Markkos89 Apr 1, 2024
325d1a7
chore: updated node version to use
Markkos89 Apr 1, 2024
1947171
feat: update manifest.json and new assets
Markkos89 Apr 1, 2024
1336ec8
fix: typo on manifest.json file, added image extension
Markkos89 Apr 1, 2024
8be212c
Merge pull request #224 from Developer-DAO/feat/manifest-fix
Markkos89 Apr 1, 2024
99e6915
Merge branch 'staging' of https://github.com/Developer-DAO/academy-tu…
Markkos89 Apr 1, 2024
5a687bb
feat: created data table and installed new ui components
Markkos89 Apr 2, 2024
c585139
feat: implemented datatable on tags
Markkos89 Apr 2, 2024
bf3da4f
feat: start porting lesson 5 to rainbowkit starter and typeScript
briangershon Feb 5, 2024
f33866c
feat: document code changes
briangershon Feb 14, 2024
b32f159
fix: mismatched ``` and change jsx to tsx
briangershon Feb 19, 2024
b1405c0
fix: lock starter to 0.2 version
briangershon Feb 19, 2024
52d3fb8
feat: add new TierNFT component
briangershon Feb 19, 2024
8358bf8
feat: add and explain TierNFT.tsx
briangershon Mar 4, 2024
8e5b8e8
feat: add display components and explanation
briangershon Mar 4, 2024
ebd8575
feat: describe hook functionality
briangershon Mar 4, 2024
006eef8
chore: update wagmi links to 1.x.wagmi.sh docs since we're using v1
briangershon Mar 4, 2024
70fdd48
feat: remove code snippets
briangershon Mar 4, 2024
44837ac
feat: remove old instructions and
briangershon Mar 24, 2024
95f8d06
chore: remove text related to Success modal
briangershon Mar 24, 2024
8936f91
feat: explain useAwaitMintResults
briangershon Mar 24, 2024
28ad4be
chore: clean-up component patterns
briangershon Mar 24, 2024
704234e
feat: support multiple authors
briangershon Mar 25, 2024
7d31788
chore: text tweaks
briangershon Mar 25, 2024
e70d7f6
Merge pull request #226 from Developer-DAO/feat/admin-panel-tanstackt…
Markkos89 Apr 3, 2024
982745d
feat: new ui version for admin dashboard testing
Markkos89 Apr 9, 2024
ec193e6
feat: added contributors page, add new contributor form
Markkos89 Apr 9, 2024
3b72533
feat: contributors table, create contributors, add contributions to l…
Markkos89 Apr 9, 2024
629d475
feat: saving checkpoint ui fixes
Markkos89 Apr 16, 2024
681a2da
chore: remove references to now non-existent scripts directory
elPiablo Apr 19, 2024
f6af685
feat: assign author to track
Markkos89 Apr 19, 2024
3ba355b
feat: add tags on lesson feature
Markkos89 Apr 19, 2024
dfd38aa
feat: change rpc and network chain id to amoy
elPiablo Apr 19, 2024
12a3581
feat: update assets, paths, code, urls, texts
elPiablo Apr 20, 2024
2e46e10
feat: format headers and outro. create lesson summary
elPiablo Apr 23, 2024
a762234
feat: update titles of other lessons when they're referenced in intro
elPiablo Apr 23, 2024
59ade10
Merge pull request #229 from Developer-DAO/feat/admin-panel-newversion
Markkos89 Apr 23, 2024
2d13b95
feat: add amoy testnet changes and remove Mumbai
briangershon Apr 28, 2024
03b9ee6
chore: starting academy fetching implementation
Markkos89 Apr 29, 2024
283dbdc
Merge pull request #231 from Developer-DAO/feat/admin-panel-newversion
Markkos89 May 9, 2024
93e4352
Merge pull request #232 from Developer-DAO/feat/admin-panel
Markkos89 May 9, 2024
f503c1c
feat: added request modal and saving email to db
Markkos89 May 16, 2024
fdc3157
feat: small ux enhancements
Markkos89 May 16, 2024
20e0dd6
feat: more work on the email verification flow. Sending email, genera…
Markkos89 May 21, 2024
a10b18e
feat: verification email flow completed
Markkos89 May 24, 2024
7781101
feat: fixed build error
Markkos89 May 24, 2024
748f084
feat: fixed build error
Markkos89 May 24, 2024
e181b8f
feat: hotfix testing with piablo
Markkos89 May 25, 2024
5083543
feat: hotfix testing with piablo
Markkos89 May 25, 2024
b6e36db
feat: hotfix testing with piablo
Markkos89 May 25, 2024
ca6e9ba
feat: hotfix testing with piablo
Markkos89 May 25, 2024
a52f2c9
feat: hotfix testing with piablo
Markkos89 May 25, 2024
599bf23
chore: round of text changes based on feedback
briangershon May 26, 2024
92820e1
chore: update images. Fix alignment on home page when not yet connected
briangershon May 27, 2024
dbf66e6
chore: update img_1 screenshot sans connect wallet button
briangershon May 27, 2024
44038e3
chore: explain Amoy changes
briangershon May 27, 2024
1c1ecae
chore: bring in code that is being explained and move hook descriptio…
briangershon May 27, 2024
7cc4f2f
chore: better explanations of the index changes about to be made
briangershon May 27, 2024
879f980
Merge pull request #228 from Developer-DAO/update-tiernft-testnet
elPiablo May 28, 2024
6c9fab9
feat: abstracted the email request and code verifications steps in tw…
Markkos89 May 29, 2024
f338b80
Merge pull request #233 from Developer-DAO/feat/emailverification-v2
Markkos89 May 29, 2024
091a933
chore: add suggested changes for index.tsx
briangershon May 29, 2024
61b23c5
Merge pull request #131 from Developer-DAO/move-to-rainbowkit-starter
briangershon May 29, 2024
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
Prev Previous commit
Next Next commit
feat: more work on the email verification flow. Sending email, genera…
…ting code, and more
Markkos89 committed May 21, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 20e0dd695955699593f99bd8d2f0936b289a8544
1 change: 1 addition & 0 deletions apps/academy/package.json
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
"@next/font": "^14.1.0",
"@rainbow-me/rainbowkit": "^1.0.10",
"@rainbow-me/rainbowkit-siwe-next-auth": "^0.3.0",
"@sendgrid/mail": "^8.1.3",
"@t3-oss/env-nextjs": "^0.6.1",
"@tanstack/react-query": "^4.33.0",
"@trpc/client": "^10.38.1",
83 changes: 67 additions & 16 deletions apps/academy/src/components/RequestEmailDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Dispatch, type SetStateAction, useState } from "react";
import { Button } from "ui";
import { Button, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "ui";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "ui";
import { Input } from "ui";
import { Label } from "ui";
@@ -16,16 +16,22 @@ export function RequestEmailDialog({ open, setIsOpen }: Props) {
const { toast } = useToast();
const [userEmail, setUserEmail] = useState("");
const [saveBtnClicked, setSaveBtnClicked] = useState(false);
const [showInputOtp, setShowInputOtp] = useState(false);
const [numberToVerify, setNumberToVerify] = useState("");

const { mutate: saveUserEmail } = api.user.addEmail.useMutation({
const { mutate: saveUserEmail, data: userData } = api.user.addEmail.useMutation({
onSuccess: () => {
toast({
title: "Amazing!",
description: "Now Check your inbox to verify your email address",
});
setSaveBtnClicked(true);
const timer = setTimeout(() => { setIsOpen(false); }, 350);
return () => { clearTimeout(timer); };
const timer = setTimeout(() => {
setShowInputOtp(true);
}, 350);
return () => {
clearTimeout(timer);
};
},
});

@@ -36,6 +42,24 @@ export function RequestEmailDialog({ open, setIsOpen }: Props) {
}
};

const handleVerifyVerificationNumber = () => {
console.log("handleVerifyVerificationNumber");
if (userData?.verificationNumber !== null && userData?.verificationNumber !== undefined) {
const verificationCorrect = Number(numberToVerify) === userData.verificationNumber;
if (verificationCorrect) {
// TODO: mutate save emailVerificated field with the date verified - trigger the toast inside the onSuccess callback from the mutation
toast({
title: "Email verified!",
description:
"Thank you so much for verifying you email address, keep learning now. Enjoy!",
});
setIsOpen(false);
} else {
//TODO: what to do? resend another email with another number?
}
}
};

return (
<Dialog open={open}>
<DialogContent
@@ -51,28 +75,55 @@ export function RequestEmailDialog({ open, setIsOpen }: Props) {
}}
>
<DialogHeader className="gap-2">
<DialogTitle className="text-3xl text-[#44AF96]">Configure your email</DialogTitle>
<DialogTitle className="text-3xl text-[#44AF96]">
{showInputOtp ? "Insert the verification code sent" : "Configure your email"}
</DialogTitle>
</DialogHeader>
<div className="flex h-fit flex-col gap-10">
<div className="flex flex-col gap-10">
<Label htmlFor="collaborators" className="text-left text-sm text-[#999999]">
Your email address will be used to receive notifications about updates, join the
frens!
</Label>
<Input
id="email"
type="email" //TODO: VALIDATE EMAIL FORMAT VALID EMAIL FORMAT https://ui.shadcn.com/docs/components/input#form
placeholder="Keep up to date with Academy's updates!"
className="col-span-3 border-0 bg-[#333333] text-[#999999]"
value={userEmail}
onChange={(e) => {
setUserEmail(e.target.value);
}}
/>
{showInputOtp ? (
<InputOTP maxLength={6} onChange={(val) => { setNumberToVerify(val); }}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
) : (
<Input
id="email"
type="email" //TODO: VALIDATE EMAIL FORMAT VALID EMAIL FORMAT https://ui.shadcn.com/docs/components/input#form
placeholder="Keep up to date with Academy's updates!"
className="col-span-3 border-0 bg-[#333333] text-[#999999]"
value={userEmail}
onChange={(e) => {
setUserEmail(e.target.value);
}}
/>
)}
</div>
</div>
<DialogFooter className="w-full justify-end">
{!saveBtnClicked ? (
{showInputOtp ? (
<Button
variant="primary"
disabled={numberToVerify.length !== 6}
onClick={handleVerifyVerificationNumber}
className="disabled:bg-gray-600 disabled:hover:bg-gray-500"
>
Verify Email
</Button>
) : !saveBtnClicked ? (
<Button
variant="primary"
disabled={!userEmail}
2 changes: 2 additions & 0 deletions apps/academy/src/env.mjs
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ const server = z.object({
// DISCORD_CLIENT_ID: z.string(),
// DISCORD_CLIENT_SECRET: z.string(),
ENVIRONMENT: z.enum(["local", "staging", "production"]),
SENDGRID_API_KEY: z.string().min(1),
});

/**
@@ -47,6 +48,7 @@ const processEnv = {
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
NEXT_PUBLIC_WALLET_CONNECT_ID: process.env["NEXT_PUBLIC_WALLET_CONNECT_ID"],
ENVIRONMENT: process.env["ENVIRONMENT"],
SENDGRID_API_KEY: process.env["SENDGRID_API_KEY"],
};

// Don't touch the part below
38 changes: 38 additions & 0 deletions apps/academy/src/pages/api/email/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import sgMail from "@sendgrid/mail";
import type { NextApiRequest, NextApiResponse } from "next";

import { env } from "@/env.mjs";

sgMail.setApiKey(env.SENDGRID_API_KEY);

interface ResponseData {
message: string;
}

export default function handler(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
if (req.method === "POST") {
const { body }: { body: { email: string; verificationNumber: string } } = req;
// Process a POST request
const msg = {
to: body.email,
from: "no-reply@developerdao.com",
subject: "D_D Academy Verification Code",
text: "This is your verification code, please insert it in the modal on Academy web",
html: `<strong>${body.verificationNumber}</strong>`,
};

sgMail
.send(msg)
.then(() => {
console.log("Email sent successfully");
res.status(200).json({ message: "success" });
})
.catch((error: any) => {
console.error(error);
res.status(200).json({ message: error });
});
} else {
// Handle any GET method
res.status(200).json({ message: "Hellooooo" });
}
}
11 changes: 11 additions & 0 deletions apps/academy/src/server/api/routers/user.ts
Original file line number Diff line number Diff line change
@@ -32,6 +32,17 @@ export const userRouter = createTRPCRouter({
email: input,
},
});

//TODO: send email with the verificationNumber autogenerated from the database field in the body
await fetch("/api/email", {
method: "POST",
body: JSON.stringify({ verificationNumber: emailSaved.verificationNumber, email: input }),
}).then(async (res) => {
await res.json().then((res) => {
console.log({ res });
});
});

return emailSaved;
}),
});
16 changes: 15 additions & 1 deletion apps/academy/src/server/auth.ts
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ declare module "next-auth" {
user: {
id: string;
image: string;
verificationNumber: number;
// ...other properties
// role: UserRole;
} & DefaultSession["user"];
@@ -120,6 +121,7 @@ export const authOptions: (ctxReq: CtxOrReq) => NextAuthOptions = ({
data: {
address: fields.address,
image: "https://www.developerdao.com/D_D_logo-dark.svg",
verificationNumber: Math.floor(100000 + Math.random() * 900000),
},
});

@@ -132,10 +134,22 @@ export const authOptions: (ctxReq: CtxOrReq) => NextAuthOptions = ({
providerAccountId: fields.address,
},
});
} else {
if (user.verificationNumber === null) {
await prisma.user.update({
where: {
id: user.id,
},
data: {
verificationNumber: Math.floor(100000 + Math.random() * 900000),
},
});
}
}
const { verificationNumber: _verificationNumber, ...restUser } = user;

return {
...user,
...restUser,
};
} catch (error) {
// Uncomment or add logging if needed
3 changes: 2 additions & 1 deletion packages/database/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ generator client {
datasource db {
provider = "postgresql"
url = env("POSTGRES_PRISMA_URL")
directUrl = env("POSTGRES_URL_NON_POOLING")
directUrl = env("POSTGRES_PRISMA_URL")
shadowDatabaseUrl = env("POSTGRES_URL_NON_POOLING")
}

@@ -33,6 +33,7 @@ model User {
address String? @unique
email String? @unique
emailVerified DateTime?
verificationNumber Int?
image String?
createdAt DateTime @default(now())
accounts Account[]
5 changes: 5 additions & 0 deletions packages/tailwindcss-config/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -79,10 +79,15 @@ module.exports = {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"caret-blink": {
"0%,70%,100%": { opacity: "1" },
"20%,50%": { opacity: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"caret-blink": "caret-blink 1.25s ease-out infinite",
},
},
},
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
@@ -65,6 +65,7 @@
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-table": "^8.15.3",
"input-otp": "^1.2.4",
"react-hook-form": "^7.49.3"
}
}
1 change: 1 addition & 0 deletions packages/ui/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ export * from "./ui/dropdown-menu";
export * from "./ui/form";
export * from "./ui/heading";
export * from "./ui/input";
export * from "./ui/input-otp";
export * from "./ui/label";
export * from "./ui/modal";
export * from "./ui/scroll-area";
71 changes: 71 additions & 0 deletions packages/ui/src/components/ui/input-otp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client";

import { OTPInput, OTPInputContext } from "input-otp";
import { Dot } from "lucide-react";
import * as React from "react";

import { cn } from "../../lib/utils";

const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName,
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
));
InputOTP.displayName = "InputOTP";

const InputOTPGroup = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center", className)} {...props} />
));
InputOTPGroup.displayName = "InputOTPGroup";

const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]!;

return (
<div
ref={ref}
className={cn(
"border-input relative flex h-10 w-10 items-center justify-center border-y border-r text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "ring-ring ring-offset-background z-10 ring-2",
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
</div>
)}
</div>
);
});
InputOTPSlot.displayName = "InputOTPSlot";

const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
));
InputOTPSeparator.displayName = "InputOTPSeparator";

export { InputOTP, InputOTPGroup, InputOTPSeparator,InputOTPSlot };
Loading