Skip to content

Commit c00603a

Browse files
committed
feat: update login page animation
1 parent d52b509 commit c00603a

File tree

12 files changed

+84
-39
lines changed

12 files changed

+84
-39
lines changed

public/3141-6662-planet-intro.riv

5.97 KB
Binary file not shown.

public/809-1634-rocket-demo.riv

4.6 KB
Binary file not shown.

src/apis/gql/gql.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const documents = {
7474
types.UpdateAvatarDocument,
7575
'\n mutation UpdateName($name: String) {\n updateName(name: $name)\n }\n ':
7676
types.UpdateNameDocument,
77-
'\n mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {\n updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)\n }\n ':
77+
'\n mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {\n updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)\n }\n':
7878
types.UpdatePasswordDocument,
7979
'\n query JsonStorage($paths: [String!]) {\n jsonStorage(paths: $paths)\n }\n ':
8080
types.JsonStorageDocument,
@@ -304,8 +304,8 @@ export function graphql(
304304
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
305305
*/
306306
export function graphql(
307-
source: '\n mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {\n updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)\n }\n '
308-
): (typeof documents)['\n mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {\n updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)\n }\n ']
307+
source: '\n mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {\n updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)\n }\n'
308+
): (typeof documents)['\n mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {\n updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)\n }\n']
309309
/**
310310
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
311311
*/

src/apis/mutation.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -807,22 +807,18 @@ export const useUpdateNameMutation = () => {
807807
})
808808
}
809809

810+
export const updatePasswordMutation = graphql(`
811+
mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {
812+
updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)
813+
}
814+
`)
815+
810816
export const useUpdatePasswordMutation = () => {
811817
const gqlClient = useGraphqlClient()
812818

813819
return useMutation({
814820
mutationFn: ({ currentPassword, newPassword }: { currentPassword: string; newPassword: string }) => {
815-
return gqlClient.request(
816-
graphql(`
817-
mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {
818-
updatePassword(currentPassword: $currentPassword, newPassword: $newPassword)
819-
}
820-
`),
821-
{
822-
currentPassword,
823-
newPassword
824-
}
825-
)
821+
return gqlClient.request(updatePasswordMutation, { currentPassword, newPassword })
826822
}
827823
})
828824
}

src/app/(auth)/login/page.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client'
22

3-
import { DotLottiePlayer } from '@dotlottie/react-player'
43
import { zodResolver } from '@hookform/resolvers/zod'
54
import { Button, Input } from '@nextui-org/react'
5+
import RiveComponent from '@rive-app/react-canvas'
66
import ky from 'ky'
77
import { useRouter } from 'next/navigation'
88
import { useForm } from 'react-hook-form'
@@ -22,9 +22,11 @@ export default function LoginPage() {
2222
})
2323

2424
return (
25-
<div className="mx-auto flex w-full max-w-screen-xl flex-col items-center gap-4 px-4 sm:flex-row sm:pt-32">
25+
<div className="mx-auto flex w-full max-w-screen-xl flex-col items-center gap-4 px-4 pt-4 sm:flex-row sm:pt-32">
2626
<div className="flex w-full flex-col items-center sm:w-2/5">
27-
<DotLottiePlayer src="/animation_lny15oo1.lottie" autoplay loop />
27+
<div className="overflow-hidden rounded-lg">
28+
<RiveComponent src="/809-1634-rocket-demo.riv" className="h-64 w-64" />
29+
</div>
2830

2931
<LogoText />
3032
</div>

src/app/(protected)/layout.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import jwt from 'jsonwebtoken'
2-
import { cookies } from 'next/headers'
31
import { redirect } from 'next/navigation'
42
import { ReactNode } from 'react'
53
import { Providers } from '~/app/(protected)/providers'
64
import { Header } from '~/components/Header'
5+
import { decodeJWTFromCookie } from '~/helpers'
76

87
export default function ProtectedLayout({ children }: { children: ReactNode }) {
9-
const jwtToken = cookies().get('jwtToken')
8+
const jwtPayload = decodeJWTFromCookie()
109

11-
if (!jwtToken) redirect('/login')
10+
if (!jwtPayload) redirect('/login')
1211

13-
const { endpointURL, token } = jwt.decode(jwtToken.value, { json: true })!
12+
const { endpointURL, token } = jwtPayload
1413

1514
return (
1615
<Providers endpointURL={endpointURL} token={token}>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { request } from 'graphql-request'
2+
import { NextResponse } from 'next/server'
3+
import { UpdatePasswordMutation } from '~/apis/gql/graphql'
4+
import { updatePasswordMutation } from '~/apis/mutation'
5+
import { decodeJWTFromCookie, storeJWTAsCookie } from '~/helpers'
6+
7+
export const POST = async (req: Request) => {
8+
const jwtPayload = decodeJWTFromCookie()
9+
10+
if (!jwtPayload) {
11+
return new NextResponse(null, { status: 401 })
12+
}
13+
14+
const { currentPassword, newPassword } = await req.json()
15+
16+
const { endpointURL, token } = jwtPayload
17+
18+
const { updatePassword } = await request<UpdatePasswordMutation>(
19+
endpointURL,
20+
updatePasswordMutation,
21+
{ currentPassword, newPassword },
22+
{ Authorization: `Bearer ${token}` }
23+
)
24+
25+
storeJWTAsCookie(endpointURL, updatePassword)
26+
27+
return new NextResponse()
28+
}

src/components/Header.tsx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,12 @@ import { FC, useEffect, useState } from 'react'
3434
import { useForm } from 'react-hook-form'
3535
import { useTranslation } from 'react-i18next'
3636
import { z } from 'zod'
37-
import { useUpdatePasswordMutation } from '~/apis/mutation'
3837
import { useUserQuery } from '~/apis/query'
3938
import { LogoText } from '~/components/LogoText'
4039
import { ModalConfirmFormFooter } from '~/components/Modal'
4140
import { updatePasswordFormDefault, useUpdatePasswordSchemaWithRefine } from '~/schemas/account'
4241

43-
const Header: FC = () => {
42+
export const Header: FC = () => {
4443
const { t } = useTranslation()
4544
const { theme: curTheme, setTheme } = useTheme()
4645

@@ -53,8 +52,6 @@ const Header: FC = () => {
5352
onOpenChange: onUpdatePasswordOpenChange
5453
} = useDisclosure()
5554

56-
const updatePasswordMutation = useUpdatePasswordMutation()
57-
5855
const updatePasswordSchemaWithRefine = useUpdatePasswordSchemaWithRefine()()
5956

6057
const updatePasswordForm = useForm<z.infer<typeof updatePasswordSchemaWithRefine>>({
@@ -204,9 +201,8 @@ const Header: FC = () => {
204201
<form
205202
onSubmit={updatePasswordForm.handleSubmit(async (values) => {
206203
try {
207-
await updatePasswordMutation.mutateAsync({
208-
currentPassword: values.oldPassword,
209-
newPassword: values.newPassword
204+
await ky.post('/api/update-password', {
205+
json: { currentPassword: values.currentPassword, newPassword: values.newPassword }
210206
})
211207

212208
onUpdatePasswordClose()
@@ -220,10 +216,10 @@ const Header: FC = () => {
220216
<div className="flex flex-col gap-4">
221217
<Input
222218
type="password"
223-
label={t('primitives.oldPassword')}
224-
placeholder={t('primitives.oldPassword')}
225-
errorMessage={updatePasswordForm.formState.errors.oldPassword?.message}
226-
{...updatePasswordForm.register('oldPassword')}
219+
label={t('primitives.currentPassword')}
220+
placeholder={t('primitives.currentPassword')}
221+
errorMessage={updatePasswordForm.formState.errors.currentPassword?.message}
222+
{...updatePasswordForm.register('currentPassword')}
227223
/>
228224

229225
<Input
@@ -255,5 +251,3 @@ const Header: FC = () => {
255251
)
256252
}
257253
Header.displayName = 'Header'
258-
259-
export { Header }

src/helpers/index.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,28 @@
1+
import jwt from 'jsonwebtoken'
2+
import { cookies } from 'next/headers'
3+
14
export const randomUnsplashImageURL = (sig: string, width: number, height: number) =>
25
`https://source.unsplash.com/random/${width}x${height}?goose&sig=${sig}`
6+
7+
export const decodeJWTFromCookie = () => {
8+
const jwtToken = cookies().get('jwtToken')
9+
10+
if (!jwtToken) return null
11+
12+
const { endpointURL, token } = jwt.decode(jwtToken.value, { json: true })! as { endpointURL: string; token: string }
13+
14+
return {
15+
endpointURL,
16+
token
17+
}
18+
}
19+
20+
export const storeJWTAsCookie = (endpointURL: string, token: string) => {
21+
const jwtToken = jwt.sign({ endpointURL, token }, process.env.NEXT_PUBLIC_JWT_SECRET!)
22+
23+
cookies().set('jwtToken', jwtToken, {
24+
maxAge: 60 * 60 * 24 * 30, // 30 days
25+
httpOnly: true,
26+
path: '/'
27+
})
28+
}

src/i18n/locales/en-US.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"address": "Address",
119119
"action": "Action",
120120
"policy": "Policy",
121-
"oldPassword": "Old Password",
121+
"currentPassword": "Current Password",
122122
"newPassword": "New Password",
123123
"confirmPassword": "Confirm Password"
124124
}

0 commit comments

Comments
 (0)