Add user avatar URL
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { TradeChart, CardPositionItem } from '..'
|
||||
import { Backtest, MoneyManagement } from '../../../generated/ManagingApi'
|
||||
import { CardPosition, CardText } from '../../mollecules'
|
||||
import {CardPositionItem, TradeChart} from '..'
|
||||
import {Backtest} from '../../../generated/ManagingApi'
|
||||
import {CardPosition, CardText} from '../../mollecules'
|
||||
|
||||
interface IBacktestRowDetailsProps {
|
||||
backtest: Backtest;
|
||||
@@ -21,7 +21,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
strategiesValues,
|
||||
signals,
|
||||
statistics,
|
||||
moneyManagement
|
||||
config
|
||||
} = backtest;
|
||||
|
||||
return (
|
||||
@@ -71,8 +71,8 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
<CardText
|
||||
title="Money Management"
|
||||
content={
|
||||
"SL: " +(moneyManagement?.stopLoss * 100).toFixed(2) + "% TP: " +
|
||||
(moneyManagement?.takeProfit * 100).toFixed(2) + "%"
|
||||
"SL: " +(config.moneyManagement?.stopLoss * 100).toFixed(2) + "% TP: " +
|
||||
(config.moneyManagement?.takeProfit * 100).toFixed(2) + "%"
|
||||
}
|
||||
></CardText>
|
||||
<CardText
|
||||
|
||||
@@ -2323,6 +2323,45 @@ export class UserClient extends AuthorizedApiBase {
|
||||
}
|
||||
return Promise.resolve<User>(null as any);
|
||||
}
|
||||
|
||||
user_UpdateAvatarUrl(avatarUrl: string): Promise<User> {
|
||||
let url_ = this.baseUrl + "/User/avatar";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(avatarUrl);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processUser_UpdateAvatarUrl(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processUser_UpdateAvatarUrl(response: Response): Promise<User> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as User;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<User>(null as any);
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkflowClient extends AuthorizedApiBase {
|
||||
@@ -2519,6 +2558,7 @@ export interface User {
|
||||
name?: string | null;
|
||||
accounts?: Account[] | null;
|
||||
agentName?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface Balance {
|
||||
|
||||
@@ -10,8 +10,13 @@ type UpdateAgentNameForm = {
|
||||
agentName: string
|
||||
}
|
||||
|
||||
type UpdateAvatarForm = {
|
||||
avatarUrl: string
|
||||
}
|
||||
|
||||
function UserInfoSettings() {
|
||||
const [showUpdateModal, setShowUpdateModal] = useState(false)
|
||||
const [showAvatarModal, setShowAvatarModal] = useState(false)
|
||||
const queryClient = useQueryClient()
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const api = new UserClient({}, apiUrl)
|
||||
@@ -22,12 +27,18 @@ function UserInfoSettings() {
|
||||
})
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
register: registerAgentName,
|
||||
handleSubmit: handleSubmitAgentName,
|
||||
formState: { errors: agentNameErrors },
|
||||
} = useForm<UpdateAgentNameForm>()
|
||||
|
||||
const onSubmit = async (data: UpdateAgentNameForm) => {
|
||||
const {
|
||||
register: registerAvatar,
|
||||
handleSubmit: handleSubmitAvatar,
|
||||
formState: { errors: avatarErrors },
|
||||
} = useForm<UpdateAvatarForm>()
|
||||
|
||||
const onSubmitAgentName = async (data: UpdateAgentNameForm) => {
|
||||
const toast = new Toast('Updating agent name')
|
||||
try {
|
||||
await api.user_UpdateAgentName(data.agentName)
|
||||
@@ -40,6 +51,19 @@ function UserInfoSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmitAvatar = async (data: UpdateAvatarForm) => {
|
||||
const toast = new Toast('Updating avatar')
|
||||
try {
|
||||
await api.user_UpdateAvatarUrl(data.avatarUrl)
|
||||
queryClient.invalidateQueries({ queryKey: ['user'] })
|
||||
setShowAvatarModal(false)
|
||||
toast.update('success', 'Avatar updated successfully')
|
||||
} catch (error) {
|
||||
console.error('Error updating avatar:', error)
|
||||
toast.update('error', 'Failed to update avatar')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<div className="bg-base-200 rounded-lg p-6 shadow-lg">
|
||||
@@ -61,13 +85,36 @@ function UserInfoSettings() {
|
||||
Update Agent Name
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="font-semibold">Avatar:</label>
|
||||
<div className="mt-2 flex items-center space-x-4">
|
||||
{user?.avatarUrl ? (
|
||||
<img
|
||||
src={user.avatarUrl}
|
||||
alt="User avatar"
|
||||
className="w-16 h-16 rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-16 h-16 rounded-full bg-base-300 flex items-center justify-center">
|
||||
<span className="text-2xl">{user?.name?.[0]?.toUpperCase() || '?'}</span>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setShowAvatarModal(true)}
|
||||
>
|
||||
Update Avatar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
showModal={showUpdateModal}
|
||||
onClose={() => setShowUpdateModal(false)}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
onSubmit={handleSubmitAgentName(onSubmitAgentName)}
|
||||
titleHeader="Update Agent Name"
|
||||
>
|
||||
<div className="form-control w-full">
|
||||
@@ -77,13 +124,50 @@ function UserInfoSettings() {
|
||||
<input
|
||||
type="text"
|
||||
className="input input-bordered w-full"
|
||||
{...register('agentName', { required: 'Agent name is required' })}
|
||||
{...registerAgentName('agentName', { required: 'Agent name is required' })}
|
||||
defaultValue={user?.agentName || ''}
|
||||
/>
|
||||
{errors.agentName && (
|
||||
{agentNameErrors.agentName && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error">
|
||||
{errors.agentName.message}
|
||||
{agentNameErrors.agentName.message}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<button type="submit" className="btn">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
showModal={showAvatarModal}
|
||||
onClose={() => setShowAvatarModal(false)}
|
||||
onSubmit={handleSubmitAvatar(onSubmitAvatar)}
|
||||
titleHeader="Update Avatar"
|
||||
>
|
||||
<div className="form-control w-full">
|
||||
<label className="label">
|
||||
<span className="label-text">Avatar URL (JPEG or PNG)</span>
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
className="input input-bordered w-full"
|
||||
{...registerAvatar('avatarUrl', {
|
||||
required: 'Avatar URL is required',
|
||||
pattern: {
|
||||
value: /^https?:\/\/.+\.(jpeg|jpg|png)$/i,
|
||||
message: 'URL must be a valid image URL ending in .jpeg, .jpg, or .png'
|
||||
}
|
||||
})}
|
||||
defaultValue={user?.avatarUrl || ''}
|
||||
/>
|
||||
{avatarErrors.avatarUrl && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error">
|
||||
{avatarErrors.avatarUrl.message}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user