forked from PlanB-Network/bitcoin-learning-management-system
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: can send email & validate token
- Loading branch information
Showing
43 changed files
with
674 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
**/node_modules | ||
|
||
.turbo | ||
.vite |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,5 @@ | |
}, | ||
"search.exclude": { | ||
"**/tsconfig.*.tsbuildinfo": true | ||
}, | ||
"cSpell.words": ["bytea", "zoomable"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import type { EnvConfig } from '@sovereign-university/types'; | ||
|
||
const getenv = <T extends string | number | boolean | null = string>( | ||
name: string, | ||
fallback?: T, | ||
): T => { | ||
const value = process.env[name] ?? ''; | ||
|
||
// If the value is empty and no fallback is provided, throw an error | ||
if (!value && fallback === undefined) { | ||
throw new Error(`Missing mandatory value for "${name}"`); | ||
} | ||
|
||
// If the value is empty and a fallback is provided, log a warning | ||
if (!value && fallback !== null) { | ||
console.warn( | ||
`No value found for "${name}"${fallback !== null ? `, defaulting to '${JSON.stringify(fallback)}'` : '!'}`, | ||
); | ||
} | ||
|
||
// If the value is not empty, parse it to the correct type (inferred from fallback type) | ||
if (fallback !== null) { | ||
switch (typeof fallback) { | ||
case 'boolean': | ||
return (value ? value === 'true' : fallback) as T; | ||
case 'number': | ||
return (parseInt(value) || fallback) as T; | ||
} | ||
} | ||
|
||
return value as T; | ||
}; | ||
|
||
/** | ||
* Real application domain (without trailing slash) | ||
*/ | ||
export const domain = getenv<string>('DOMAIN', 'http://localhost:8181'); | ||
|
||
export const sendgrid: EnvConfig['sendgrid'] = { | ||
key: getenv<string | null>('SENDGRID_KEY', null), | ||
enable: getenv<boolean>('SENDGRID_ENABLE', false), | ||
email: getenv<string | null>('SENDGRID_EMAIL', null), | ||
templates: { | ||
emailChange: getenv<string | null>( | ||
'SENDGRID_EMAIL_CHANGE_TEMPLATE_ID', | ||
null, | ||
), | ||
recoverPassword: getenv<string | null>( | ||
'SENDGRID_RECOVER_PASSWORD_TEMPLATE_ID', | ||
null, | ||
), | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
apps/web/src/features/dashboard/components/change-email-modal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import type { FormikHelpers } from 'formik'; | ||
import { Formik } from 'formik'; | ||
import { t } from 'i18next'; | ||
import { isEmpty } from 'lodash-es'; | ||
import { useCallback } from 'react'; | ||
import { ZodError, z } from 'zod'; | ||
|
||
import { Button } from '@sovereign-university/ui'; | ||
|
||
import { Modal } from '../../../atoms/Modal/index.tsx'; | ||
import { TextInput } from '../../../atoms/TextInput/index.tsx'; | ||
import { trpc } from '../../../utils/index.ts'; | ||
|
||
const changeEmailSchema = z.object({ | ||
email: z.string(), | ||
}); | ||
|
||
interface ChangePasswordModalProps { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
email: string; | ||
onEmailSent: () => void; | ||
} | ||
|
||
type ChangeEmailForm = z.infer<typeof changeEmailSchema>; | ||
|
||
export const ChangeEmailModal = ({ | ||
isOpen, | ||
onClose, | ||
email, | ||
onEmailSent, | ||
}: ChangePasswordModalProps) => { | ||
const changeEmail = trpc.user.changeEmail.useMutation({ | ||
onSuccess: () => { | ||
onClose(); | ||
onEmailSent(); | ||
}, | ||
}); | ||
|
||
const handleChangeEmail = useCallback( | ||
async (form: ChangeEmailForm, actions: FormikHelpers<ChangeEmailForm>) => { | ||
const errors = await actions.validateForm(); | ||
if (!isEmpty(errors)) return; | ||
|
||
changeEmail.mutate(form); | ||
}, | ||
[changeEmail], | ||
); | ||
|
||
return ( | ||
<Modal | ||
isOpen={isOpen} | ||
onClose={onClose} | ||
headerText={t('settings.changeEmail')} | ||
> | ||
<div className="flex flex-col items-center"> | ||
<Formik | ||
initialValues={{ email }} | ||
validate={(values) => { | ||
try { | ||
changeEmailSchema.parse(values); | ||
} catch (error) { | ||
if (error instanceof ZodError) { | ||
return error.flatten().fieldErrors; | ||
} | ||
} | ||
}} | ||
onSubmit={handleChangeEmail} | ||
> | ||
{({ | ||
handleSubmit, | ||
handleChange, | ||
handleBlur, | ||
values, | ||
errors, | ||
touched, | ||
}) => ( | ||
<form | ||
onSubmit={(event) => { | ||
event.preventDefault(); | ||
handleSubmit(); | ||
}} | ||
className="flex w-full flex-col items-center pb-6" | ||
> | ||
<div className="flex w-full flex-col"> | ||
<TextInput | ||
name="email" | ||
type="email" | ||
autoComplete="email" | ||
labelText="Email" | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
value={values.email} | ||
className="w-80" | ||
error={touched.email ? errors.email : null} | ||
/> | ||
</div> | ||
|
||
{changeEmail.error && ( | ||
<p className="mt-2 text-base font-semibold text-red-300"> | ||
{changeEmail.error.message} | ||
</p> | ||
)} | ||
|
||
<Button className="mt-6" rounded type="submit"> | ||
{t('words.update')} | ||
</Button> | ||
</form> | ||
)} | ||
</Formik> | ||
</div> | ||
</Modal> | ||
); | ||
}; |
Oops, something went wrong.