updat users
This commit is contained in:
@@ -173,6 +173,9 @@ def primary(
|
||||
now_ = now()
|
||||
expr = 'SET email_primary = :email_primary, updated_at = :now'
|
||||
|
||||
if new_email == old_email:
|
||||
return JSONResponse(status_code=HTTPStatus.NO_CONTENT)
|
||||
|
||||
with dyn.transact_writer() as transact:
|
||||
# Set the old email as non-primary
|
||||
transact.update(
|
||||
|
||||
@@ -12,7 +12,7 @@ export default [
|
||||
route('orders', 'routes/orders.tsx'),
|
||||
route('settings', 'routes/settings/layout.tsx', [
|
||||
index('routes/settings/profile.tsx'),
|
||||
route('emails', 'routes/settings/emails.tsx'),
|
||||
route('emails', 'routes/settings/emails/index.tsx'),
|
||||
route('password', 'routes/settings/password.tsx')
|
||||
]),
|
||||
route('konviva', 'routes/konviva.ts'),
|
||||
|
||||
@@ -18,13 +18,13 @@ import { useOutletContext } from 'react-router'
|
||||
import type { User } from '@repo/ui/routes/users/data'
|
||||
import { useRevalidator } from 'react-router'
|
||||
|
||||
const formSchema = z.object({
|
||||
export const formSchema = z.object({
|
||||
email: z.email('Email inválido').trim().toLowerCase()
|
||||
})
|
||||
|
||||
type Schema = z.infer<typeof formSchema>
|
||||
export type Schema = z.infer<typeof formSchema>
|
||||
|
||||
export function AddEmail() {
|
||||
export function Add() {
|
||||
const { revalidate } = useRevalidator()
|
||||
const { user } = useOutletContext() as { user: User }
|
||||
const { runAsync } = useRequest(
|
||||
@@ -85,7 +85,10 @@ export function AddEmail() {
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="relative overflow-hidden">
|
||||
<Button
|
||||
type="submit"
|
||||
className="relative overflow-hidden cursor-pointer"
|
||||
>
|
||||
{formState.isSubmitting && (
|
||||
<div className="absolute inset-0 bg-lime-500 flex items-center justify-center">
|
||||
<Spinner />
|
||||
@@ -1,6 +1,13 @@
|
||||
import { z } from 'zod'
|
||||
import { isValidCPF } from '@brazilian-utils/brazilian-utils'
|
||||
|
||||
export type Email = {
|
||||
sk: string
|
||||
email: string
|
||||
email_verified: boolean
|
||||
email_primary: boolean
|
||||
}
|
||||
|
||||
const isName = (name: string) => name && name.includes(' ')
|
||||
|
||||
export const formSchema = z.object({
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Route } from './+types/emails'
|
||||
import type { Route } from './+types/index'
|
||||
|
||||
import {
|
||||
Suspense,
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
createContext,
|
||||
use
|
||||
} from 'react'
|
||||
|
||||
import { Await } from 'react-router'
|
||||
import { Await, useRevalidator } from 'react-router'
|
||||
import { EllipsisIcon, CircleXIcon, SendIcon } from 'lucide-react'
|
||||
import { useRequest, useToggle } from 'ahooks'
|
||||
import { toast } from 'sonner'
|
||||
@@ -51,16 +50,11 @@ import {
|
||||
ItemContent,
|
||||
ItemTitle
|
||||
} from '@repo/ui/components/ui/item'
|
||||
import { AddEmail } from './add-email'
|
||||
import { Badge } from '@repo/ui/components/ui/badge'
|
||||
import { useRevalidator } from 'react-router'
|
||||
|
||||
type Email = {
|
||||
sk: string
|
||||
email: string
|
||||
email_verified: boolean
|
||||
email_primary: boolean
|
||||
}
|
||||
import { type Email } from './data'
|
||||
import { Add } from './add'
|
||||
import { Primary } from './primary'
|
||||
|
||||
const ActionMenuContext = createContext<Email | null>(null)
|
||||
|
||||
@@ -81,13 +75,14 @@ export default function Route({ loaderData: { data } }: Route.ComponentProps) {
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<Await resolve={data}>
|
||||
{({ items = [] }) => (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Emails</CardTitle>
|
||||
<CardDescription>
|
||||
Você pode associar vários emails a sua conta. É possível usar
|
||||
qualquer email para recuperar a senha, mas apenas o email
|
||||
principal receberá as mensagens.
|
||||
Você pode associar vários emails a sua conta. É possível
|
||||
usar qualquer email para recuperar a senha, mas apenas o
|
||||
email principal receberá as mensagens.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-2.5">
|
||||
@@ -137,9 +132,12 @@ export default function Route({ loaderData: { data } }: Route.ComponentProps) {
|
||||
</CardContent>
|
||||
|
||||
<CardContent>
|
||||
<AddEmail />
|
||||
<Add />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Primary items={items} />
|
||||
</>
|
||||
)}
|
||||
</Await>
|
||||
</Suspense>
|
||||
@@ -174,10 +172,21 @@ type ItemProps = ComponentProps<typeof DropdownMenuItem> & {
|
||||
}
|
||||
|
||||
function ResendItem({ onSuccess }: ItemProps) {
|
||||
const { user } = useOutletContext() as { user: User }
|
||||
const { email, email_verified } = use(ActionMenuContext) as Email
|
||||
const { runAsync, loading } = useRequest(
|
||||
async () => {
|
||||
return await new Promise((r) => setTimeout(r, 3000))
|
||||
const r = await fetch(
|
||||
`/api/users/${user.id}/emails/${email}/request-verification`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: new Headers({ 'Content-Type': 'application/json' })
|
||||
}
|
||||
)
|
||||
|
||||
if (!r.ok) {
|
||||
throw await r.json()
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useRequest } from 'ahooks'
|
||||
import { useRevalidator } from 'react-router'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '@repo/ui/components/ui/card'
|
||||
import { Button } from '@repo/ui/components/ui/button'
|
||||
import { Kbd } from '@repo/ui/components/ui/kbd'
|
||||
import {
|
||||
NativeSelect,
|
||||
NativeSelectOption
|
||||
} from '@repo/ui/components/ui/native-select'
|
||||
import { useOutletContext } from 'react-router'
|
||||
import type { User } from '@repo/ui/routes/users/data'
|
||||
|
||||
import type { Email } from './data'
|
||||
import { type Schema, formSchema } from './add'
|
||||
|
||||
export function Primary({ items = [] }: { items: Email[] }) {
|
||||
const emails = items.map((props) => {
|
||||
const [, email] = props.sk.split('#') as [string, string]
|
||||
return { ...props, email }
|
||||
}) as Email[]
|
||||
const primary = emails.find((e) => e.email_primary === true)
|
||||
const { revalidate } = useRevalidator()
|
||||
const { user } = useOutletContext() as { user: User }
|
||||
const { runAsync } = useRequest(
|
||||
async ({ email }) => {
|
||||
// Doesn't use `user` because the data could be outdated
|
||||
const selected = emails.find((e) => e.email === email)
|
||||
const r = await fetch(`/api/users/${user.id}/emails/primary`, {
|
||||
method: 'PATCH',
|
||||
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify({
|
||||
new_email: selected?.email,
|
||||
old_email: primary?.email,
|
||||
email_verified: selected?.email_verified
|
||||
})
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw await r.json()
|
||||
}
|
||||
},
|
||||
{ manual: true }
|
||||
)
|
||||
|
||||
const { handleSubmit, register } = useForm({
|
||||
resolver: zodResolver(formSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async ({ email }: Schema) => {
|
||||
try {
|
||||
await runAsync({ email })
|
||||
toast.success(`O email principal foi alterado para: ${email}.`)
|
||||
revalidate()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Email principal</CardTitle>
|
||||
<CardDescription>
|
||||
<Kbd className="font-mono border">
|
||||
<span className="truncate max-lg:max-w-62">{primary?.email}</span>
|
||||
</Kbd>{' '}
|
||||
será usado para mensagens e pode ser usado para redefinições de senha.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="flex gap-1.5">
|
||||
<NativeSelect defaultValue={primary?.email} {...register('email')}>
|
||||
{emails.map(({ email }) => {
|
||||
return (
|
||||
<NativeSelectOption key={email} value={email}>
|
||||
{email}
|
||||
</NativeSelectOption>
|
||||
)
|
||||
})}
|
||||
</NativeSelect>
|
||||
<Button type="submit" className="overflow-hidden cursor-pointer">
|
||||
Alterar
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import { Input } from '@repo/ui/components/ui/input'
|
||||
import { Spinner } from '@repo/ui/components/ui/spinner'
|
||||
import { type User } from '@repo/ui/routes/users/data'
|
||||
|
||||
import { formSchema, type Schema } from './data'
|
||||
import { formSchema, type Schema } from './emails/data'
|
||||
import { useFetcher } from 'react-router'
|
||||
|
||||
export async function action({ request }: Route.ActionArgs) {
|
||||
|
||||
@@ -76,7 +76,7 @@ export function NavUser({
|
||||
<Avatar className="size-10">
|
||||
<AvatarFallback>{initials(user.name)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<ChevronDown className="size-3.5 absolute -bottom-px -right-px bg-neutral-700 border border-background rounded-full px-px" />
|
||||
<ChevronDown className="size-3.5 absolute -bottom-px -right-px bg-gray-200 dark:bg-neutral-700 border border-background rounded-full px-px" />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
|
||||
@@ -18,7 +18,7 @@ Para proteger sua conta na EDUSEG, precisamos apenas verificar seu
|
||||
endereço de email: {email}.<br/><br/>
|
||||
|
||||
<a href="https://saladeaula.digital/settings/emails/{code}/verify">
|
||||
👉 Verificar endereço de email
|
||||
👉 Clique aqui para verificar endereço de email
|
||||
</a>
|
||||
"""
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Oi {first_name}, tudo bem?<br/><br/>
|
||||
Sua conta foi criada na EDUSEG pela empresa <b>{org_name}</b>.<br/><br/>
|
||||
|
||||
<a href="https://id.saladeaula.digital/signup?uid={user_id}&code={code}">
|
||||
👉 Faça agora seu primeiro acesso
|
||||
👉 Clique aqui para fazer seu primeiro acesso
|
||||
</a>
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user