From 7f2fcca7a8c1cecc5f767299d92f925092b52845 Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 25 Sep 2024 17:39:59 +0800 Subject: [PATCH] feat: add settings page --- backend/src/auth/router.py | 6 +- backend/src/auth/schemas.py | 4 + .../user/profile/category-form.tsx | 74 ++ .../user/profile/change-password-form.tsx | 179 +++ .../app/(authenticated)/user/profile/page.tsx | 28 + frontend/client/client.ts | 6 +- frontend/client/core/types.ts | 46 +- frontend/client/index.ts | 6 +- frontend/client/schemas.gen.ts | 1132 ++++++++--------- frontend/client/types.gen.ts | 372 +++--- frontend/queries/user.ts | 23 + 11 files changed, 1088 insertions(+), 788 deletions(-) create mode 100644 frontend/app/(authenticated)/user/profile/category-form.tsx create mode 100644 frontend/app/(authenticated)/user/profile/change-password-form.tsx create mode 100644 frontend/app/(authenticated)/user/profile/page.tsx diff --git a/backend/src/auth/router.py b/backend/src/auth/router.py index fc789e86..16f66153 100644 --- a/backend/src/auth/router.py +++ b/backend/src/auth/router.py @@ -18,6 +18,7 @@ from src.common.dependencies import get_session from .schemas import ( PasswordResetCompleteData, + PasswordResetMoreCompleteData, PasswordResetRequestData, SignUpData, UserPublic, @@ -27,6 +28,7 @@ authenticate_user, get_current_user, get_password_hash, + verify_password, ) from .models import AccountType, PasswordReset, User @@ -189,9 +191,11 @@ def complete_password_reset( @router.put("/change-password") def change_password( user: Annotated[User, Depends(get_current_user)], - data: PasswordResetCompleteData, + data: PasswordResetMoreCompleteData, session=Depends(get_session), ): + if not verify_password(data.old_password, user.hashed_password): + raise HTTPException(HTTPStatus.UNAUTHORIZED) user.hashed_password = get_password_hash(data.password) session.add(user) session.commit() diff --git a/backend/src/auth/schemas.py b/backend/src/auth/schemas.py index b447b218..335a9e86 100644 --- a/backend/src/auth/schemas.py +++ b/backend/src/auth/schemas.py @@ -37,3 +37,7 @@ def check_passwords_match(self): if pw1 is not None and pw2 is not None and pw1 != pw2: raise ValueError("passwords do not match") return self + + +class PasswordResetMoreCompleteData(PasswordResetCompleteData): + old_password: str diff --git a/frontend/app/(authenticated)/user/profile/category-form.tsx b/frontend/app/(authenticated)/user/profile/category-form.tsx new file mode 100644 index 00000000..a2aea096 --- /dev/null +++ b/frontend/app/(authenticated)/user/profile/category-form.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import clsx from "clsx"; + +import { Button } from "@/components/ui/button"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; +import { getCategories } from "@/queries/category"; +import { useUpdateProfile } from "@/queries/user"; +import { getCategoryFor } from "@/types/categories"; + +interface Props { + initialCategoryIds: number[]; +} + +export default function CategoryForm({ initialCategoryIds }: Props) { + const { data: categories, isLoading } = useQuery(getCategories()); + const [categoryIds, setCategoryIds] = useState(initialCategoryIds); + + const toggleCategory = (id: number) => { + if (!categoryIds.includes(id)) { + setCategoryIds([...categoryIds, id]); + } else { + setCategoryIds(categoryIds.filter((item) => item !== id)); + } + }; + + const updateProfileMutation = useUpdateProfile(); + + const handleSubmit = () => { + updateProfileMutation.mutate({ categoryIds }); + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+
+

Categories

+

+ Select the General Paper categories you are interested in. +

+
+ {categories!.map((category) => { + const isActive = categoryIds.includes(category.id); + return ( +
toggleCategory(category.id)} + > + {getCategoryFor(category.name)} +
+ ); + })} +
+ +
+
+ ); +} diff --git a/frontend/app/(authenticated)/user/profile/change-password-form.tsx b/frontend/app/(authenticated)/user/profile/change-password-form.tsx new file mode 100644 index 00000000..25865bf1 --- /dev/null +++ b/frontend/app/(authenticated)/user/profile/change-password-form.tsx @@ -0,0 +1,179 @@ +import { useState } from "react"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Check, CircleAlert } from "lucide-react"; +import { z } from "zod"; + +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Box } from "@/components/ui/box"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { useChangePassword } from "@/queries/user"; + +const changePasswordCompleteFormSchema = z + .object({ + password: z.string(), + confirmPassword: z.string(), + oldPassword: z.string(), + }) + .refine(({ password, confirmPassword }) => password === confirmPassword, { + message: "Passwords must match", + path: ["confirmPassword"], + }); + +type ChangePasswordRequestForm = z.infer< + typeof changePasswordCompleteFormSchema +>; + +const changePasswordFormDefault = { + password: "", + confirmPassword: "", + oldPassword: "", +}; + +export default function ChangePasswordForm() { + const [isError, setIsError] = useState(false); + const [success, setSuccess] = useState(false); + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + + const form = useForm({ + resolver: zodResolver(changePasswordCompleteFormSchema), + defaultValues: changePasswordFormDefault, + }); + const changePasswordMutation = useChangePassword(); + + const onSubmit: SubmitHandler = (data) => { + changePasswordMutation.mutate(data, { + onSuccess: async (data) => { + if (data.error) { + setIsError(true); + setSuccess(false); + } else { + setIsError(false); + setSuccess(true); + } + }, + }); + }; + + return ( +
+
+

Change password

+ + {isError && ( + + + Wrong password. Try again? + + )} + {success && ( + + + + Successfully changed password. + + + )} +
+ +
+ + ( + + + Old password + + + + + + + )} + /> + +
+
+ + ( + + + New Password + + + + + + + )} + /> + +
+
+ + ( + + + Confirm Password + + + + + + + )} + /> + +
+
+ + setIsPasswordVisible( + checkedState === "indeterminate" ? false : checkedState, + ) + } + /> + +
+ +
+ +
+
+
+ ); +} diff --git a/frontend/app/(authenticated)/user/profile/page.tsx b/frontend/app/(authenticated)/user/profile/page.tsx new file mode 100644 index 00000000..84e666d6 --- /dev/null +++ b/frontend/app/(authenticated)/user/profile/page.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useUserStore } from "@/store/user/user-store-provider"; + +import CategoryForm from "./category-form"; +import ChangePasswordForm from "./change-password-form"; + +export default function Profile() { + const user = useUserStore((store) => store.user); + + return ( + user && ( +
+
+

Settings

+
+ +
+ category.id)} + /> + + +
+
+ ) + ); +} diff --git a/frontend/client/client.ts b/frontend/client/client.ts index 6d6722dd..7d794d27 100644 --- a/frontend/client/client.ts +++ b/frontend/client/client.ts @@ -1,4 +1,4 @@ -export { createClient } from "./core/"; +export { createClient } from './core/'; export type { Client, Config, @@ -6,10 +6,10 @@ export type { RequestOptions, RequestOptionsBase, RequestResult, -} from "./core/types"; +} from './core/types'; export { createConfig, formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer, -} from "./core/utils"; +} from './core/utils'; diff --git a/frontend/client/core/types.ts b/frontend/client/core/types.ts index 18a974f1..f31a609e 100644 --- a/frontend/client/core/types.ts +++ b/frontend/client/core/types.ts @@ -6,14 +6,14 @@ import type { AxiosResponse, AxiosStatic, CreateAxiosDefaults, -} from "axios"; +} from 'axios'; -import type { BodySerializer } from "./utils"; +import type { BodySerializer } from './utils'; type OmitKeys = Pick>; export interface Config - extends Omit { + extends Omit { /** * Axios implementation. You can use this option to provide a custom * Axios instance. @@ -38,7 +38,7 @@ export interface Config * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} */ headers?: - | CreateAxiosDefaults["headers"] + | CreateAxiosDefaults['headers'] | Record< string, | string @@ -55,15 +55,15 @@ export interface Config * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} */ method?: - | "connect" - | "delete" - | "get" - | "head" - | "options" - | "patch" - | "post" - | "put" - | "trace"; + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; /** * A function for transforming response data before it's returned to the * caller function. This is an ideal place to post-process server data, @@ -100,7 +100,7 @@ type MethodFn = < TError = unknown, ThrowOnError extends boolean = false, >( - options: Omit, "method">, + options: Omit, 'method'>, ) => RequestResult; type RequestFn = < @@ -108,8 +108,8 @@ type RequestFn = < TError = unknown, ThrowOnError extends boolean = false, >( - options: Omit, "method"> & - Pick>, "method">, + options: Omit, 'method'> & + Pick>, 'method'>, ) => RequestResult; export interface Client { @@ -128,12 +128,12 @@ export interface Client { export type RequestOptions = RequestOptionsBase & Config & { - headers: AxiosRequestConfig["headers"]; + headers: AxiosRequestConfig['headers']; }; type OptionsBase = Omit< RequestOptionsBase, - "url" + 'url' > & { /** * You can provide a client instance returned by `createClient()` instead of @@ -148,12 +148,12 @@ export type Options< ThrowOnError extends boolean = boolean, > = T extends { body?: any } ? T extends { headers?: any } - ? OmitKeys, "body" | "headers"> & T - : OmitKeys, "body"> & + ? OmitKeys, 'body' | 'headers'> & T + : OmitKeys, 'body'> & T & - Pick, "headers"> + Pick, 'headers'> : T extends { headers?: any } - ? OmitKeys, "headers"> & + ? OmitKeys, 'headers'> & T & - Pick, "body"> + Pick, 'body'> : OptionsBase & T; diff --git a/frontend/client/index.ts b/frontend/client/index.ts index 1cb041de..0a2b84ba 100644 --- a/frontend/client/index.ts +++ b/frontend/client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./schemas.gen"; -export * from "./services.gen"; -export * from "./types.gen"; +export * from './schemas.gen'; +export * from './services.gen'; +export * from './types.gen'; \ No newline at end of file diff --git a/frontend/client/schemas.gen.ts b/frontend/client/schemas.gen.ts index 71735405..1e2516db 100644 --- a/frontend/client/schemas.gen.ts +++ b/frontend/client/schemas.gen.ts @@ -1,659 +1,645 @@ // This file is auto-generated by @hey-api/openapi-ts export const AnalysisDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - category: { - $ref: "#/components/schemas/CategoryDTO", - }, - content: { - type: "string", - title: "Content", - }, - }, - type: "object", - required: ["id", "category", "content"], - title: "AnalysisDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + category: { + '$ref': '#/components/schemas/CategoryDTO' + }, + content: { + type: 'string', + title: 'Content' + } + }, + type: 'object', + required: ['id', 'category', 'content'], + title: 'AnalysisDTO' } as const; export const AnswerDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - points: { - items: { - $ref: "#/components/schemas/PointMiniDTO", - }, - type: "array", - title: "Points", - }, - }, - type: "object", - required: ["id", "points"], - title: "AnswerDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + points: { + items: { + '$ref': '#/components/schemas/PointMiniDTO' + }, + type: 'array', + title: 'Points' + } + }, + type: 'object', + required: ['id', 'points'], + title: 'AnswerDTO' } as const; export const ArticleDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - title: { - type: "string", - title: "Title", - }, - summary: { - type: "string", - title: "Summary", - }, - body: { - type: "string", - title: "Body", - }, - url: { - type: "string", - title: "Url", - }, - source: { - $ref: "#/components/schemas/ArticleSource", - }, - date: { - type: "string", - format: "date-time", - title: "Date", - }, - image_url: { - type: "string", - title: "Image Url", - }, - }, - type: "object", - required: [ - "id", - "title", - "summary", - "body", - "url", - "source", - "date", - "image_url", - ], - title: "ArticleDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + title: { + type: 'string', + title: 'Title' + }, + summary: { + type: 'string', + title: 'Summary' + }, + body: { + type: 'string', + title: 'Body' + }, + url: { + type: 'string', + title: 'Url' + }, + source: { + '$ref': '#/components/schemas/ArticleSource' + }, + date: { + type: 'string', + format: 'date-time', + title: 'Date' + }, + image_url: { + type: 'string', + title: 'Image Url' + } + }, + type: 'object', + required: ['id', 'title', 'summary', 'body', 'url', 'source', 'date', 'image_url'], + title: 'ArticleDTO' } as const; export const ArticleSourceSchema = { - type: "string", - enum: ["CNA", "GUARDIAN"], - title: "ArticleSource", + type: 'string', + enum: ['CNA', 'GUARDIAN'], + title: 'ArticleSource' } as const; export const Body_log_in_auth_login_postSchema = { - properties: { - grant_type: { - anyOf: [ - { - type: "string", - pattern: "password", - }, - { - type: "null", - }, - ], - title: "Grant Type", - }, - username: { - type: "string", - title: "Username", - }, - password: { - type: "string", - title: "Password", - }, - scope: { - type: "string", - title: "Scope", - default: "", - }, - client_id: { - anyOf: [ - { - type: "string", + properties: { + grant_type: { + anyOf: [ + { + type: 'string', + pattern: 'password' + }, + { + type: 'null' + } + ], + title: 'Grant Type' }, - { - type: "null", + username: { + type: 'string', + title: 'Username' }, - ], - title: "Client Id", - }, - client_secret: { - anyOf: [ - { - type: "string", + password: { + type: 'string', + title: 'Password' }, - { - type: "null", + scope: { + type: 'string', + title: 'Scope', + default: '' }, - ], - title: "Client Secret", - }, - }, - type: "object", - required: ["username", "password"], - title: "Body_log_in_auth_login_post", + client_id: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Client Id' + }, + client_secret: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Client Secret' + } + }, + type: 'object', + required: ['username', 'password'], + title: 'Body_log_in_auth_login_post' } as const; export const CategoryDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - name: { - type: "string", - title: "Name", - }, - }, - type: "object", - required: ["id", "name"], - title: "CategoryDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + name: { + type: 'string', + title: 'Name' + } + }, + type: 'object', + required: ['id', 'name'], + title: 'CategoryDTO' } as const; export const CreateUserQuestionSchema = { - properties: { - question: { - type: "string", - title: "Question", - }, - }, - type: "object", - required: ["question"], - title: "CreateUserQuestion", + properties: { + question: { + type: 'string', + title: 'Question' + } + }, + type: 'object', + required: ['question'], + title: 'CreateUserQuestion' } as const; export const EventDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - title: { - type: "string", - title: "Title", - }, - description: { - type: "string", - title: "Description", - }, - is_singapore: { - type: "boolean", - title: "Is Singapore", - }, - date: { - type: "string", - format: "date-time", - title: "Date", - }, - categories: { - items: { - $ref: "#/components/schemas/CategoryDTO", - }, - type: "array", - title: "Categories", - }, - original_article: { - $ref: "#/components/schemas/ArticleDTO", - }, - reads: { - items: { - $ref: "#/components/schemas/ReadDTO", - }, - type: "array", - title: "Reads", - }, - analysises: { - items: { - $ref: "#/components/schemas/AnalysisDTO", - }, - type: "array", - title: "Analysises", - }, - gp_questions: { - items: { - $ref: "#/components/schemas/GPQuestionDTO", - }, - type: "array", - title: "Gp Questions", - }, - }, - type: "object", - required: [ - "id", - "title", - "description", - "is_singapore", - "date", - "categories", - "original_article", - "reads", - "analysises", - "gp_questions", - ], - title: "EventDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + title: { + type: 'string', + title: 'Title' + }, + description: { + type: 'string', + title: 'Description' + }, + is_singapore: { + type: 'boolean', + title: 'Is Singapore' + }, + date: { + type: 'string', + format: 'date-time', + title: 'Date' + }, + categories: { + items: { + '$ref': '#/components/schemas/CategoryDTO' + }, + type: 'array', + title: 'Categories' + }, + original_article: { + '$ref': '#/components/schemas/ArticleDTO' + }, + reads: { + items: { + '$ref': '#/components/schemas/ReadDTO' + }, + type: 'array', + title: 'Reads' + }, + analysises: { + items: { + '$ref': '#/components/schemas/AnalysisDTO' + }, + type: 'array', + title: 'Analysises' + }, + gp_questions: { + items: { + '$ref': '#/components/schemas/GPQuestionDTO' + }, + type: 'array', + title: 'Gp Questions' + } + }, + type: 'object', + required: ['id', 'title', 'description', 'is_singapore', 'date', 'categories', 'original_article', 'reads', 'analysises', 'gp_questions'], + title: 'EventDTO' } as const; export const EventIndexResponseSchema = { - properties: { - total_count: { - type: "integer", - title: "Total Count", - }, - count: { - type: "integer", - title: "Count", - }, - data: { - items: { - $ref: "#/components/schemas/MiniEventDTO", - }, - type: "array", - title: "Data", - }, - }, - type: "object", - required: ["total_count", "count", "data"], - title: "EventIndexResponse", + properties: { + total_count: { + type: 'integer', + title: 'Total Count' + }, + count: { + type: 'integer', + title: 'Count' + }, + data: { + items: { + '$ref': '#/components/schemas/MiniEventDTO' + }, + type: 'array', + title: 'Data' + } + }, + type: 'object', + required: ['total_count', 'count', 'data'], + title: 'EventIndexResponse' } as const; export const GPQuestionDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - question: { - type: "string", - title: "Question", - }, - is_llm_generated: { - type: "boolean", - title: "Is Llm Generated", - }, - categories: { - items: { - $ref: "#/components/schemas/CategoryDTO", - }, - type: "array", - title: "Categories", - }, - }, - type: "object", - required: ["id", "question", "is_llm_generated", "categories"], - title: "GPQuestionDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + question: { + type: 'string', + title: 'Question' + }, + is_llm_generated: { + type: 'boolean', + title: 'Is Llm Generated' + }, + categories: { + items: { + '$ref': '#/components/schemas/CategoryDTO' + }, + type: 'array', + title: 'Categories' + } + }, + type: 'object', + required: ['id', 'question', 'is_llm_generated', 'categories'], + title: 'GPQuestionDTO' } as const; export const HTTPValidationErrorSchema = { - properties: { - detail: { - items: { - $ref: "#/components/schemas/ValidationError", - }, - type: "array", - title: "Detail", - }, - }, - type: "object", - title: "HTTPValidationError", + properties: { + detail: { + items: { + '$ref': '#/components/schemas/ValidationError' + }, + type: 'array', + title: 'Detail' + } + }, + type: 'object', + title: 'HTTPValidationError' } as const; export const MiniEventDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - title: { - type: "string", - title: "Title", - }, - description: { - type: "string", - title: "Description", - }, - is_singapore: { - type: "boolean", - title: "Is Singapore", - }, - date: { - type: "string", - format: "date-time", - title: "Date", - }, - categories: { - items: { - $ref: "#/components/schemas/CategoryDTO", - }, - type: "array", - title: "Categories", - }, - original_article: { - $ref: "#/components/schemas/ArticleDTO", - }, - reads: { - items: { - $ref: "#/components/schemas/ReadDTO", - }, - type: "array", - title: "Reads", - }, - }, - type: "object", - required: [ - "id", - "title", - "description", - "is_singapore", - "date", - "categories", - "original_article", - "reads", - ], - title: "MiniEventDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + title: { + type: 'string', + title: 'Title' + }, + description: { + type: 'string', + title: 'Description' + }, + is_singapore: { + type: 'boolean', + title: 'Is Singapore' + }, + date: { + type: 'string', + format: 'date-time', + title: 'Date' + }, + categories: { + items: { + '$ref': '#/components/schemas/CategoryDTO' + }, + type: 'array', + title: 'Categories' + }, + original_article: { + '$ref': '#/components/schemas/ArticleDTO' + }, + reads: { + items: { + '$ref': '#/components/schemas/ReadDTO' + }, + type: 'array', + title: 'Reads' + } + }, + type: 'object', + required: ['id', 'title', 'description', 'is_singapore', 'date', 'categories', 'original_article', 'reads'], + title: 'MiniEventDTO' } as const; export const NoteCreateSchema = { - properties: { - content: { - type: "string", - title: "Content", - }, - start_index: { - type: "integer", - title: "Start Index", - }, - end_index: { - type: "integer", - title: "End Index", - }, - parent_id: { - type: "integer", - title: "Parent Id", - }, - parent_type: { - $ref: "#/components/schemas/NoteType", + properties: { + content: { + type: 'string', + title: 'Content' + }, + start_index: { + type: 'integer', + title: 'Start Index' + }, + end_index: { + type: 'integer', + title: 'End Index' + }, + parent_id: { + type: 'integer', + title: 'Parent Id' + }, + parent_type: { + '$ref': '#/components/schemas/NoteType' + } }, - }, - type: "object", - required: ["content", "start_index", "end_index", "parent_id", "parent_type"], - title: "NoteCreate", + type: 'object', + required: ['content', 'start_index', 'end_index', 'parent_id', 'parent_type'], + title: 'NoteCreate' } as const; export const NoteDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - content: { - type: "string", - title: "Content", - }, - start_index: { - type: "integer", - title: "Start Index", - }, - end_index: { - type: "integer", - title: "End Index", - }, - parent_id: { - type: "integer", - title: "Parent Id", - }, - parent_type: { - $ref: "#/components/schemas/NoteType", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + content: { + type: 'string', + title: 'Content' + }, + start_index: { + type: 'integer', + title: 'Start Index' + }, + end_index: { + type: 'integer', + title: 'End Index' + }, + parent_id: { + type: 'integer', + title: 'Parent Id' + }, + parent_type: { + '$ref': '#/components/schemas/NoteType' + } }, - }, - type: "object", - required: [ - "id", - "content", - "start_index", - "end_index", - "parent_id", - "parent_type", - ], - title: "NoteDTO", + type: 'object', + required: ['id', 'content', 'start_index', 'end_index', 'parent_id', 'parent_type'], + title: 'NoteDTO' } as const; export const NoteTypeSchema = { - type: "string", - enum: ["event", "article", "point"], - title: "NoteType", + type: 'string', + enum: ['event', 'article', 'point'], + title: 'NoteType' } as const; export const NoteUpdateSchema = { - properties: { - content: { - type: "string", - title: "Content", - }, - start_index: { - type: "integer", - title: "Start Index", - }, - end_index: { - type: "integer", - title: "End Index", - }, - }, - type: "object", - required: ["content", "start_index", "end_index"], - title: "NoteUpdate", + properties: { + content: { + type: 'string', + title: 'Content' + }, + start_index: { + type: 'integer', + title: 'Start Index' + }, + end_index: { + type: 'integer', + title: 'End Index' + } + }, + type: 'object', + required: ['content', 'start_index', 'end_index'], + title: 'NoteUpdate' } as const; export const PasswordResetCompleteDataSchema = { - properties: { - password: { - type: "string", - minLength: 6, - title: "Password", - }, - confirm_password: { - type: "string", - minLength: 6, - title: "Confirm Password", - }, - }, - type: "object", - required: ["password", "confirm_password"], - title: "PasswordResetCompleteData", + properties: { + password: { + type: 'string', + minLength: 6, + title: 'Password' + }, + confirm_password: { + type: 'string', + minLength: 6, + title: 'Confirm Password' + } + }, + type: 'object', + required: ['password', 'confirm_password'], + title: 'PasswordResetCompleteData' +} as const; + +export const PasswordResetMoreCompleteDataSchema = { + properties: { + password: { + type: 'string', + minLength: 6, + title: 'Password' + }, + confirm_password: { + type: 'string', + minLength: 6, + title: 'Confirm Password' + }, + old_password: { + type: 'string', + title: 'Old Password' + } + }, + type: 'object', + required: ['password', 'confirm_password', 'old_password'], + title: 'PasswordResetMoreCompleteData' } as const; export const PasswordResetRequestDataSchema = { - properties: { - email: { - type: "string", - format: "email", - title: "Email", - }, - }, - type: "object", - required: ["email"], - title: "PasswordResetRequestData", + properties: { + email: { + type: 'string', + format: 'email', + title: 'Email' + } + }, + type: 'object', + required: ['email'], + title: 'PasswordResetRequestData' } as const; export const PointMiniDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - title: { - type: "string", - title: "Title", - }, - body: { - type: "string", - title: "Body", - }, - events: { - items: { - $ref: "#/components/schemas/EventDTO", - }, - type: "array", - title: "Events", - }, - }, - type: "object", - required: ["id", "title", "body", "events"], - title: "PointMiniDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + title: { + type: 'string', + title: 'Title' + }, + body: { + type: 'string', + title: 'Body' + }, + events: { + items: { + '$ref': '#/components/schemas/EventDTO' + }, + type: 'array', + title: 'Events' + } + }, + type: 'object', + required: ['id', 'title', 'body', 'events'], + title: 'PointMiniDTO' } as const; export const ProfileUpdateSchema = { - properties: { - category_ids: { - items: { - type: "integer", - }, - type: "array", - title: "Category Ids", - }, - }, - type: "object", - required: ["category_ids"], - title: "ProfileUpdate", + properties: { + category_ids: { + items: { + type: 'integer' + }, + type: 'array', + title: 'Category Ids' + } + }, + type: 'object', + required: ['category_ids'], + title: 'ProfileUpdate' } as const; export const ReadDTOSchema = { - properties: { - first_read: { - type: "string", - format: "date-time", - title: "First Read", - }, - last_read: { - type: "string", - format: "date-time", - title: "Last Read", - }, - }, - type: "object", - required: ["first_read", "last_read"], - title: "ReadDTO", + properties: { + first_read: { + type: 'string', + format: 'date-time', + title: 'First Read' + }, + last_read: { + type: 'string', + format: 'date-time', + title: 'Last Read' + } + }, + type: 'object', + required: ['first_read', 'last_read'], + title: 'ReadDTO' } as const; export const SignUpDataSchema = { - properties: { - email: { - type: "string", - format: "email", - title: "Email", - }, - password: { - type: "string", - minLength: 6, - title: "Password", - }, - }, - type: "object", - required: ["email", "password"], - title: "SignUpData", + properties: { + email: { + type: 'string', + format: 'email', + title: 'Email' + }, + password: { + type: 'string', + minLength: 6, + title: 'Password' + } + }, + type: 'object', + required: ['email', 'password'], + title: 'SignUpData' } as const; export const TokenSchema = { - properties: { - access_token: { - type: "string", - title: "Access Token", - }, - token_type: { - type: "string", - title: "Token Type", - }, - user: { - $ref: "#/components/schemas/UserPublic", + properties: { + access_token: { + type: 'string', + title: 'Access Token' + }, + token_type: { + type: 'string', + title: 'Token Type' + }, + user: { + '$ref': '#/components/schemas/UserPublic' + } }, - }, - type: "object", - required: ["access_token", "token_type", "user"], - title: "Token", + type: 'object', + required: ['access_token', 'token_type', 'user'], + title: 'Token' } as const; export const UserPublicSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - email: { - type: "string", - format: "email", - title: "Email", - }, - categories: { - items: { - $ref: "#/components/schemas/CategoryDTO", - }, - type: "array", - title: "Categories", - }, - }, - type: "object", - required: ["id", "email", "categories"], - title: "UserPublic", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + email: { + type: 'string', + format: 'email', + title: 'Email' + }, + categories: { + items: { + '$ref': '#/components/schemas/CategoryDTO' + }, + type: 'array', + title: 'Categories' + } + }, + type: 'object', + required: ['id', 'email', 'categories'], + title: 'UserPublic' } as const; export const UserQuestionMiniDTOSchema = { - properties: { - id: { - type: "integer", - title: "Id", - }, - question: { - type: "string", - title: "Question", - }, - answer: { - $ref: "#/components/schemas/AnswerDTO", + properties: { + id: { + type: 'integer', + title: 'Id' + }, + question: { + type: 'string', + title: 'Question' + }, + answer: { + '$ref': '#/components/schemas/AnswerDTO' + } }, - }, - type: "object", - required: ["id", "question", "answer"], - title: "UserQuestionMiniDTO", + type: 'object', + required: ['id', 'question', 'answer'], + title: 'UserQuestionMiniDTO' } as const; export const ValidationErrorSchema = { - properties: { - loc: { - items: { - anyOf: [ - { - type: "string", - }, - { - type: "integer", - }, - ], - }, - type: "array", - title: "Location", - }, - msg: { - type: "string", - title: "Message", - }, - type: { - type: "string", - title: "Error Type", - }, - }, - type: "object", - required: ["loc", "msg", "type"], - title: "ValidationError", -} as const; + properties: { + loc: { + items: { + anyOf: [ + { + type: 'string' + }, + { + type: 'integer' + } + ] + }, + type: 'array', + title: 'Location' + }, + msg: { + type: 'string', + title: 'Message' + }, + type: { + type: 'string', + title: 'Error Type' + } + }, + type: 'object', + required: ['loc', 'msg', 'type'], + title: 'ValidationError' +} as const; \ No newline at end of file diff --git a/frontend/client/types.gen.ts b/frontend/client/types.gen.ts index 6d267b34..dffd5c8e 100644 --- a/frontend/client/types.gen.ts +++ b/frontend/client/types.gen.ts @@ -1,375 +1,377 @@ // This file is auto-generated by @hey-api/openapi-ts export type AnalysisDTO = { - id: number; - category: CategoryDTO; - content: string; + id: number; + category: CategoryDTO; + content: string; }; export type AnswerDTO = { - id: number; - points: Array; + id: number; + points: Array; }; export type ArticleDTO = { - id: number; - title: string; - summary: string; - body: string; - url: string; - source: ArticleSource; - date: string; - image_url: string; + id: number; + title: string; + summary: string; + body: string; + url: string; + source: ArticleSource; + date: string; + image_url: string; }; -export type ArticleSource = "CNA" | "GUARDIAN"; +export type ArticleSource = 'CNA' | 'GUARDIAN'; export type Body_log_in_auth_login_post = { - grant_type?: string | null; - username: string; - password: string; - scope?: string; - client_id?: string | null; - client_secret?: string | null; + grant_type?: (string | null); + username: string; + password: string; + scope?: string; + client_id?: (string | null); + client_secret?: (string | null); }; export type CategoryDTO = { - id: number; - name: string; + id: number; + name: string; }; export type CreateUserQuestion = { - question: string; + question: string; }; export type EventDTO = { - id: number; - title: string; - description: string; - is_singapore: boolean; - date: string; - categories: Array; - original_article: ArticleDTO; - reads: Array; - analysises: Array; - gp_questions: Array; + id: number; + title: string; + description: string; + is_singapore: boolean; + date: string; + categories: Array; + original_article: ArticleDTO; + reads: Array; + analysises: Array; + gp_questions: Array; }; export type EventIndexResponse = { - total_count: number; - count: number; - data: Array; + total_count: number; + count: number; + data: Array; }; export type GPQuestionDTO = { - id: number; - question: string; - is_llm_generated: boolean; - categories: Array; + id: number; + question: string; + is_llm_generated: boolean; + categories: Array; }; export type HTTPValidationError = { - detail?: Array; + detail?: Array; }; export type MiniEventDTO = { - id: number; - title: string; - description: string; - is_singapore: boolean; - date: string; - categories: Array; - original_article: ArticleDTO; - reads: Array; + id: number; + title: string; + description: string; + is_singapore: boolean; + date: string; + categories: Array; + original_article: ArticleDTO; + reads: Array; }; export type NoteCreate = { - content: string; - start_index: number; - end_index: number; - parent_id: number; - parent_type: NoteType; + content: string; + start_index: number; + end_index: number; + parent_id: number; + parent_type: NoteType; }; export type NoteDTO = { - id: number; - content: string; - start_index: number; - end_index: number; - parent_id: number; - parent_type: NoteType; + id: number; + content: string; + start_index: number; + end_index: number; + parent_id: number; + parent_type: NoteType; }; -export type NoteType = "event" | "article" | "point"; +export type NoteType = 'event' | 'article' | 'point'; export type NoteUpdate = { - content: string; - start_index: number; - end_index: number; + content: string; + start_index: number; + end_index: number; }; export type PasswordResetCompleteData = { - password: string; - confirm_password: string; + password: string; + confirm_password: string; +}; + +export type PasswordResetMoreCompleteData = { + password: string; + confirm_password: string; + old_password: string; }; export type PasswordResetRequestData = { - email: string; + email: string; }; export type PointMiniDTO = { - id: number; - title: string; - body: string; - events: Array; + id: number; + title: string; + body: string; + events: Array; }; export type ProfileUpdate = { - category_ids: Array; + category_ids: Array<(number)>; }; export type ReadDTO = { - first_read: string; - last_read: string; + first_read: string; + last_read: string; }; export type SignUpData = { - email: string; - password: string; + email: string; + password: string; }; export type Token = { - access_token: string; - token_type: string; - user: UserPublic; + access_token: string; + token_type: string; + user: UserPublic; }; export type UserPublic = { - id: number; - email: string; - categories: Array; + id: number; + email: string; + categories: Array; }; export type UserQuestionMiniDTO = { - id: number; - question: string; - answer: AnswerDTO; + id: number; + question: string; + answer: AnswerDTO; }; export type ValidationError = { - loc: Array; - msg: string; - type: string; + loc: Array<(string | number)>; + msg: string; + type: string; }; export type SignUpAuthSignupPostData = { - body: SignUpData; + body: SignUpData; }; -export type SignUpAuthSignupPostResponse = Token; +export type SignUpAuthSignupPostResponse = (Token); -export type SignUpAuthSignupPostError = HTTPValidationError; +export type SignUpAuthSignupPostError = (HTTPValidationError); export type LogInAuthLoginPostData = { - body: Body_log_in_auth_login_post; + body: Body_log_in_auth_login_post; }; -export type LogInAuthLoginPostResponse = Token; +export type LogInAuthLoginPostResponse = (Token); -export type LogInAuthLoginPostError = HTTPValidationError; +export type LogInAuthLoginPostError = (HTTPValidationError); -export type LoginGoogleAuthLoginGoogleGetResponse = unknown; +export type LoginGoogleAuthLoginGoogleGetResponse = (unknown); export type LoginGoogleAuthLoginGoogleGetError = unknown; export type AuthGoogleAuthGoogleGetData = { - query: { - code: string; - }; + query: { + code: string; + }; }; -export type AuthGoogleAuthGoogleGetResponse = Token; +export type AuthGoogleAuthGoogleGetResponse = (Token); -export type AuthGoogleAuthGoogleGetError = HTTPValidationError; +export type AuthGoogleAuthGoogleGetError = (HTTPValidationError); export type GetUserAuthSessionGetData = unknown; -export type GetUserAuthSessionGetResponse = UserPublic; +export type GetUserAuthSessionGetResponse = (UserPublic); -export type GetUserAuthSessionGetError = HTTPValidationError; +export type GetUserAuthSessionGetError = (HTTPValidationError); -export type LogoutAuthLogoutGetResponse = unknown; +export type LogoutAuthLogoutGetResponse = (unknown); export type LogoutAuthLogoutGetError = unknown; export type RequestPasswordResetAuthPasswordResetPostData = { - body: PasswordResetRequestData; + body: PasswordResetRequestData; }; -export type RequestPasswordResetAuthPasswordResetPostResponse = unknown; +export type RequestPasswordResetAuthPasswordResetPostResponse = (unknown); -export type RequestPasswordResetAuthPasswordResetPostError = - HTTPValidationError; +export type RequestPasswordResetAuthPasswordResetPostError = (HTTPValidationError); export type CompletePasswordResetAuthPasswordResetPutData = { - body: PasswordResetCompleteData; - query: { - code: string; - }; + body: PasswordResetCompleteData; + query: { + code: string; + }; }; -export type CompletePasswordResetAuthPasswordResetPutResponse = unknown; +export type CompletePasswordResetAuthPasswordResetPutResponse = (unknown); -export type CompletePasswordResetAuthPasswordResetPutError = - HTTPValidationError; +export type CompletePasswordResetAuthPasswordResetPutError = (HTTPValidationError); export type ChangePasswordAuthChangePasswordPutData = { - body: PasswordResetCompleteData; + body: PasswordResetMoreCompleteData; }; -export type ChangePasswordAuthChangePasswordPutResponse = unknown; +export type ChangePasswordAuthChangePasswordPutResponse = (unknown); -export type ChangePasswordAuthChangePasswordPutError = HTTPValidationError; +export type ChangePasswordAuthChangePasswordPutError = (HTTPValidationError); -export type GetCategoriesCategoriesGetResponse = Array; +export type GetCategoriesCategoriesGetResponse = (Array); export type GetCategoriesCategoriesGetError = unknown; export type UpdateProfileProfilePutData = { - body: ProfileUpdate; + body: ProfileUpdate; }; -export type UpdateProfileProfilePutResponse = UserPublic; +export type UpdateProfileProfilePutResponse = (UserPublic); -export type UpdateProfileProfilePutError = HTTPValidationError; +export type UpdateProfileProfilePutError = (HTTPValidationError); export type GetEventsEventsGetData = { - query?: { - category_ids?: Array | null; - end_date?: string | null; - limit?: number | null; - offset?: number | null; - start_date?: string | null; - }; + query?: { + category_ids?: (Array<(number)> | null); + end_date?: (string | null); + limit?: (number | null); + offset?: (number | null); + start_date?: (string | null); + }; }; -export type GetEventsEventsGetResponse = EventIndexResponse; +export type GetEventsEventsGetResponse = (EventIndexResponse); -export type GetEventsEventsGetError = HTTPValidationError; +export type GetEventsEventsGetError = (HTTPValidationError); export type GetEventEventsIdGetData = { - query: { - id: number; - }; + query: { + id: number; + }; }; -export type GetEventEventsIdGetResponse = EventDTO; +export type GetEventEventsIdGetResponse = (EventDTO); -export type GetEventEventsIdGetError = HTTPValidationError; +export type GetEventEventsIdGetError = (HTTPValidationError); export type GetEventNotesEventsIdNotesGetData = { - query: { - id: number; - }; + query: { + id: number; + }; }; -export type GetEventNotesEventsIdNotesGetResponse = Array; +export type GetEventNotesEventsIdNotesGetResponse = (Array); -export type GetEventNotesEventsIdNotesGetError = HTTPValidationError; +export type GetEventNotesEventsIdNotesGetError = (HTTPValidationError); export type ReadEventEventsIdReadPostData = { - query: { - id: number; - }; + query: { + id: number; + }; }; -export type ReadEventEventsIdReadPostResponse = unknown; +export type ReadEventEventsIdReadPostResponse = (unknown); -export type ReadEventEventsIdReadPostError = HTTPValidationError; +export type ReadEventEventsIdReadPostError = (HTTPValidationError); export type SearchWhateverEventsSearchGetData = { - query: { - query: string; - }; + query: { + query: string; + }; }; -export type SearchWhateverEventsSearchGetResponse = unknown; +export type SearchWhateverEventsSearchGetResponse = (unknown); -export type SearchWhateverEventsSearchGetError = HTTPValidationError; +export type SearchWhateverEventsSearchGetError = (HTTPValidationError); export type GetUserQuestionsUserQuestionsGetData = unknown; -export type GetUserQuestionsUserQuestionsGetResponse = - Array; +export type GetUserQuestionsUserQuestionsGetResponse = (Array); -export type GetUserQuestionsUserQuestionsGetError = HTTPValidationError; +export type GetUserQuestionsUserQuestionsGetError = (HTTPValidationError); export type CreateUserQuestionUserQuestionsPostData = { - body: CreateUserQuestion; + body: CreateUserQuestion; }; -export type CreateUserQuestionUserQuestionsPostResponse = UserQuestionMiniDTO; +export type CreateUserQuestionUserQuestionsPostResponse = (UserQuestionMiniDTO); -export type CreateUserQuestionUserQuestionsPostError = HTTPValidationError; +export type CreateUserQuestionUserQuestionsPostError = (HTTPValidationError); export type GetUserQuestionUserQuestionsIdGetData = { - query: { - id: number; - }; + query: { + id: number; + }; }; -export type GetUserQuestionUserQuestionsIdGetResponse = unknown; +export type GetUserQuestionUserQuestionsIdGetResponse = (unknown); -export type GetUserQuestionUserQuestionsIdGetError = HTTPValidationError; +export type GetUserQuestionUserQuestionsIdGetError = (HTTPValidationError); export type AskGpQuestionUserQuestionsAskGpQuestionGetData = { - query: { - question: string; - }; + query: { + question: string; + }; }; -export type AskGpQuestionUserQuestionsAskGpQuestionGetResponse = unknown; +export type AskGpQuestionUserQuestionsAskGpQuestionGetResponse = (unknown); -export type AskGpQuestionUserQuestionsAskGpQuestionGetError = - HTTPValidationError; +export type AskGpQuestionUserQuestionsAskGpQuestionGetError = (HTTPValidationError); export type GetAllNotesNotesGetData = unknown; -export type GetAllNotesNotesGetResponse = Array; +export type GetAllNotesNotesGetResponse = (Array); -export type GetAllNotesNotesGetError = HTTPValidationError; +export type GetAllNotesNotesGetError = (HTTPValidationError); export type CreateNoteNotesPostData = { - body: NoteCreate; + body: NoteCreate; }; -export type CreateNoteNotesPostResponse = NoteDTO; +export type CreateNoteNotesPostResponse = (NoteDTO); -export type CreateNoteNotesPostError = HTTPValidationError; +export type CreateNoteNotesPostError = (HTTPValidationError); export type UpdateNoteNotesIdPutData = { - body: NoteUpdate; - query: { - id: number; - }; + body: NoteUpdate; + query: { + id: number; + }; }; -export type UpdateNoteNotesIdPutResponse = NoteDTO; +export type UpdateNoteNotesIdPutResponse = (NoteDTO); -export type UpdateNoteNotesIdPutError = HTTPValidationError; +export type UpdateNoteNotesIdPutError = (HTTPValidationError); export type DeleteNoteNotesIdDeleteData = { - query: { - id: number; - }; + query: { + id: number; + }; }; -export type DeleteNoteNotesIdDeleteResponse = unknown; +export type DeleteNoteNotesIdDeleteResponse = (unknown); -export type DeleteNoteNotesIdDeleteError = HTTPValidationError; +export type DeleteNoteNotesIdDeleteError = (HTTPValidationError); export type GetPointNotesPointsIdNotesGetData = unknown; -export type GetPointNotesPointsIdNotesGetResponse = unknown; +export type GetPointNotesPointsIdNotesGetResponse = (unknown); -export type GetPointNotesPointsIdNotesGetError = HTTPValidationError; +export type GetPointNotesPointsIdNotesGetError = (HTTPValidationError); \ No newline at end of file diff --git a/frontend/queries/user.ts b/frontend/queries/user.ts index 85810ef9..77b899cb 100644 --- a/frontend/queries/user.ts +++ b/frontend/queries/user.ts @@ -5,6 +5,7 @@ import { } from "@tanstack/react-query"; import { + changePasswordAuthChangePasswordPut, getUserAuthSessionGet, updateProfileProfilePut, } from "@/client/services.gen"; @@ -35,3 +36,25 @@ export const useUpdateProfile = () => { }, }); }; + +export const useChangePassword = () => { + return useMutation({ + mutationFn: ({ + password, + confirmPassword, + oldPassword, + }: { + password: string; + confirmPassword: string; + oldPassword: string; + }) => { + return changePasswordAuthChangePasswordPut({ + body: { + password, + confirm_password: confirmPassword, + old_password: oldPassword, + }, + }); + }, + }); +};