diff --git a/src/app/(protected)/account/profile/_components/ProfileMain.tsx b/src/app/(protected)/account/profile/_components/ProfileMain.tsx
deleted file mode 100644
index 6f4e949..0000000
--- a/src/app/(protected)/account/profile/_components/ProfileMain.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-/* eslint-disable @typescript-eslint/no-use-before-define */
-import { Col } from 'react-bootstrap';
-import styled from 'styled-components';
-import { Card } from '@/shared/components/Card';
-import { left } from '@/styles/directions';
-import Image from 'next/image';
-import { ProfileCard } from './ProfileBasicComponents';
-
-const ProfileMain = () => (
-
-
-
-
-
-
-
-
- Holly Hammond
- Account Manager
- holly@colony.com
- +42-452-743-233
-
-
-
-
-
-);
-
-export default ProfileMain;
-
-// region STYLES
-
-const ProfileInformation = styled.div`
- padding: 30px 40px;
- display: flex;
- text-align: ${left};
- justify-content: center;
- flex-direction: column;
- align-items: center;
-
- @media (max-width: 1345px) and (min-width: 1200px) {
- padding: 30px 15px;
- }
-
- @media screen and (max-width: 360px) {
- width: 100%;
- }
-`;
-
-const ProfileAvatar = styled.div`
- height: 140px;
- width: 140px;
- overflow: hidden;
- border-radius: 50%;
-
- img {
- height: 100%;
- }
-
- @media (max-width: 1345px) and (min-width: 1200px) {
- height: 130px;
- width: 130px;
- }
-`;
-
-const ProfileData = styled.div`
- margin-top: 30px;
-
- @media screen and (max-width: 360px) {
- width: 100%;
- display: flex;
- flex-direction: column;
- text-align: center;
- padding: 0;
- }
-`;
-
-const ProfileName = styled.p`
- font-weight: 900;
- text-transform: uppercase;
- margin: 0;
- line-height: 18px;
-`;
-
-const ProfileWork = styled.p`
- font-weight: 500;
- margin-bottom: 10px;
- margin-top: 0;
- opacity: 0.6;
- line-height: 18px;
-`;
-
-const ProfileContact = styled.p`
- margin-top: 0;
- margin-bottom: 5px;
- line-height: 18px;
-`;
-
-// endregion
diff --git a/src/app/(protected)/account/profile/page.tsx b/src/app/(protected)/account/profile/page.tsx
index ec1f323..d6cac37 100644
--- a/src/app/(protected)/account/profile/page.tsx
+++ b/src/app/(protected)/account/profile/page.tsx
@@ -1,18 +1,6 @@
-'use client';
-
-import { Container, Row } from 'react-bootstrap';
-import { useTitle } from '@/hooks/useTitle';
-import ProfileMain from './_components/ProfileMain';
+import ProfileMain from 'module/protected/account/profile/ProfileMain';
const Profile = () => {
- useTitle('Profile - BeeQuant');
-
- return (
-
-
-
-
-
- );
+ return ;
};
export default Profile;
diff --git a/src/app/(protected)/crypto/exchange/page.tsx b/src/app/(protected)/crypto/exchange/page.tsx
index 8c6fb97..3523aff 100644
--- a/src/app/(protected)/crypto/exchange/page.tsx
+++ b/src/app/(protected)/crypto/exchange/page.tsx
@@ -1,11 +1,8 @@
'use client';
import { Col, Container, Row } from 'react-bootstrap';
-import { useTitle } from '@/hooks/useTitle';
const CryptoExchanges = () => {
- useTitle('Exchanges - BeeQuant');
-
return (
diff --git a/src/graphql/codegen/gql.ts b/src/graphql/codegen/gql.ts
index 38cac59..b6915f1 100644
--- a/src/graphql/codegen/gql.ts
+++ b/src/graphql/codegen/gql.ts
@@ -17,7 +17,7 @@ const documents = {
types.LoginDocument,
'\n mutation Register($input: CreateUserInput!) {\n register(input: $input) {\n code\n message\n data\n }\n }\n':
types.RegisterDocument,
- '\n query getUserInfo {\n getUserInfo {\n id\n displayName\n }\n }\n':
+ '\n query getUserInfo {\n getUserInfo {\n id\n displayName\n email\n ref\n }\n }\n':
types.GetUserInfoDocument,
'\n query getUserById($id: String!) {\n getUserById(id: $id) {\n id\n email\n realName\n displayName\n mobile\n }\n }\n':
types.GetUserByIdDocument,
@@ -55,8 +55,8 @@ export function gql(
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(
- source: '\n query getUserInfo {\n getUserInfo {\n id\n displayName\n }\n }\n'
-): (typeof documents)['\n query getUserInfo {\n getUserInfo {\n id\n displayName\n }\n }\n'];
+ source: '\n query getUserInfo {\n getUserInfo {\n id\n displayName\n email\n ref\n }\n }\n'
+): (typeof documents)['\n query getUserInfo {\n getUserInfo {\n id\n displayName\n email\n ref\n }\n }\n'];
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
diff --git a/src/graphql/codegen/graphql.ts b/src/graphql/codegen/graphql.ts
index d53c2b1..06abeda 100644
--- a/src/graphql/codegen/graphql.ts
+++ b/src/graphql/codegen/graphql.ts
@@ -230,7 +230,13 @@ export type GetUserInfoQueryVariables = Exact<{ [key: string]: never }>;
export type GetUserInfoQuery = {
__typename?: 'Query';
- getUserInfo: { __typename?: 'UserType'; id: string; displayName: string };
+ getUserInfo: {
+ __typename?: 'UserType';
+ id: string;
+ displayName: string;
+ email: string;
+ ref: string;
+ };
};
export type GetUserByIdQueryVariables = Exact<{
@@ -375,6 +381,8 @@ export const GetUserInfoDocument = {
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'displayName' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'email' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'ref' } },
],
},
},
diff --git a/src/graphql/user.ts b/src/graphql/user.ts
index 748b58a..65192bf 100644
--- a/src/graphql/user.ts
+++ b/src/graphql/user.ts
@@ -5,6 +5,8 @@ export const GET_USER = gql(`
getUserInfo {
id
displayName
+ email
+ ref
}
}
`);
diff --git a/src/app/(protected)/account/profile/_components/ProfileBasicComponents.tsx b/src/module/protected/account/profile/ProfileBasicComponents.tsx
similarity index 100%
rename from src/app/(protected)/account/profile/_components/ProfileBasicComponents.tsx
rename to src/module/protected/account/profile/ProfileBasicComponents.tsx
diff --git a/src/module/protected/account/profile/ProfileMain.test.tsx b/src/module/protected/account/profile/ProfileMain.test.tsx
new file mode 100644
index 0000000..3048c34
--- /dev/null
+++ b/src/module/protected/account/profile/ProfileMain.test.tsx
@@ -0,0 +1,134 @@
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { MockedProvider } from '@apollo/client/testing';
+import { GET_USER, UPDATE_USER } from '@/graphql/user';
+import { AUTH_TOKEN } from '@/shared/constants/storage';
+import ProfileMain from './ProfileMain';
+
+const mocks = [
+ {
+ request: {
+ query: GET_USER,
+ },
+ result: {
+ data: {
+ getUserInfo: {
+ id: '1332332',
+ email: 'test@example.com',
+ ref: 'ref123',
+ displayName: 'Test User',
+ },
+ },
+ },
+ },
+ {
+ request: {
+ query: UPDATE_USER,
+ variables: { id: '1332332', input: { displayName: 'Updated User' } },
+ },
+ result: {
+ data: {
+ updateUser: {
+ id: '1332332',
+ displayName: 'Updated User',
+ },
+ },
+ },
+ },
+];
+
+beforeEach(() => {
+ localStorage.setItem(AUTH_TOKEN, 'mock-token');
+});
+
+test('renders profile information', async () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(screen.getByText('Profile')).toBeInTheDocument();
+ });
+
+ expect(screen.getByDisplayValue('Test User')).toBeInTheDocument();
+ expect(screen.getByText('test@example.com')).toBeInTheDocument();
+ expect(screen.getByText('ref123')).toBeInTheDocument();
+});
+
+test('updates display name-valid', async () => {
+ render(
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('Profile')).toBeInTheDocument();
+ });
+
+ const input = screen.getByDisplayValue('Test User');
+ fireEvent.change(input, { target: { value: 'Updated User' } });
+ const button = screen.getByText('Submit');
+ fireEvent.click(button);
+
+ await waitFor(() => {
+ expect(screen.getByDisplayValue('Updated User')).toBeInTheDocument();
+ });
+});
+
+test('display name validation - minimum length', async () => {
+ render(
+
+
+
+ );
+
+ const input = await screen.findByDisplayValue('Test User');
+ fireEvent.change(input, { target: { value: 'Abc' } });
+ const button = screen.getByText('Submit');
+ fireEvent.click(button);
+
+ const errorMessage = await screen.findByText(
+ 'Display name must be 4-15 characters long and contain only letters, numbers, hyphens, and underscores.'
+ );
+ expect(errorMessage).toBeInTheDocument();
+});
+
+test('display name validation - maximum length', async () => {
+ render(
+
+
+
+ );
+
+ const input = await screen.findByDisplayValue('Test User');
+ fireEvent.change(input, { target: { value: 'ThisIsAVeryLongDisplayName1231231231' } });
+ const button = screen.getByText('Submit');
+ fireEvent.click(button);
+
+ const errorMessage = await screen.findByText(
+ 'Display name must be 4-15 characters long and contain only letters, numbers, hyphens, and underscores.'
+ );
+ expect(errorMessage).toBeInTheDocument();
+});
+
+test('display name validation - invalid characters', async () => {
+ render(
+
+
+
+ );
+
+ const input = await screen.findByDisplayValue('Test User');
+ fireEvent.change(input, { target: { value: 'Invalid!' } });
+ const button = screen.getByText('Submit');
+ fireEvent.click(button);
+
+ const errorMessage = await screen.findByText(
+ 'Display name must be 4-15 characters long and contain only letters, numbers, hyphens, and underscores.'
+ );
+ expect(errorMessage).toBeInTheDocument();
+});
diff --git a/src/module/protected/account/profile/ProfileMain.tsx b/src/module/protected/account/profile/ProfileMain.tsx
new file mode 100644
index 0000000..a1155b4
--- /dev/null
+++ b/src/module/protected/account/profile/ProfileMain.tsx
@@ -0,0 +1,135 @@
+'use client';
+import { Container, Row } from 'react-bootstrap';
+import { Col } from 'react-bootstrap';
+import { useMutation, useQuery } from '@apollo/client';
+import { useEffect, useState } from 'react';
+import { Card } from '@/shared/components/Card';
+import { useUserContext } from '@/hooks/userHooks';
+import { UPDATE_USER, GET_USER } from '@/graphql/user';
+import { Button } from '@/shared/components/Button';
+import {
+ ProfileContact,
+ ProfileData,
+ ProfileInformation,
+ ProfileIntro,
+ ProfileReadOnly,
+ ProfileText,
+ Description,
+ ButtonGroup,
+ ErrorMessage,
+} from './ProfileMainStyleCom';
+import { ProfileCard } from './ProfileBasicComponents';
+
+const initialEmail = '';
+const initialRef = '';
+const initialDisplayName = '';
+
+const ProfileMain = () => {
+ const { store, setStore } = useUserContext();
+ const [userId, setUserId] = useState('');
+ const [msg, setMsg] = useState('');
+ const { loading, data } = useQuery(GET_USER);
+ const [updateUser] = useMutation(UPDATE_USER);
+ const validateUpdatedDisplayName = (name: string) => {
+ const regex = /^[a-zA-Z0-9-_]+$/;
+ return name.length >= 4 && name.length <= 15 && regex.test(name);
+ };
+ const handleUpdateUser = () => {
+ if (!validateUpdatedDisplayName(disName)) {
+ setMsg(
+ 'Display name must be 4-15 characters long and contain only letters, numbers, hyphens, and underscores.'
+ );
+ return;
+ }
+ setMsg('');
+ updateUser({
+ variables: {
+ id: userId,
+ input: {
+ displayName: disName,
+ },
+ },
+ }).then(() => {
+ setStore(() => {
+ const updatedStore = {
+ ...store,
+ displayName: disName,
+ };
+
+ return updatedStore;
+ });
+ });
+ };
+
+ const [disEmail, setDisEmail] = useState(initialEmail);
+ const [disRef, setDisRef] = useState(initialRef);
+ const [disName, setDisplayName] = useState(initialDisplayName);
+ useEffect(() => {
+ if (!loading && data) {
+ const { id, email, ref, displayName } = data.getUserInfo;
+ setUserId(id);
+ setDisEmail(email);
+ setDisRef(ref);
+ setDisplayName(displayName || initialDisplayName);
+ setStore((prevStore: Record) => {
+ const updatedStore = {
+ ...prevStore,
+ displayName,
+ email,
+ ref,
+ };
+ return updatedStore;
+ });
+ }
+ }, [loading, data]);
+
+ if (loading) {
+ return Loading...
;
+ }
+
+ return (
+
+
+
+
+
+
+
+ <>Profile>
+
+
+ <>Update your account information>
+
+
+
+ Display Name
+ setDisplayName(e.target.value)}
+ />
+
+
+ Email
+ {disEmail}
+
+
+ Reference
+ {disRef}
+
+
+
+
+
+ {msg && {msg}}
+
+
+
+
+
+
+ );
+};
+export default ProfileMain;
diff --git a/src/module/protected/account/profile/ProfileMainStyleCom.tsx b/src/module/protected/account/profile/ProfileMainStyleCom.tsx
new file mode 100644
index 0000000..f892f0c
--- /dev/null
+++ b/src/module/protected/account/profile/ProfileMainStyleCom.tsx
@@ -0,0 +1,96 @@
+import styled from 'styled-components';
+import { colorText, colorBackgroundBody, colorBackground } from '@/styles/palette';
+import { left } from '@/styles/directions';
+
+export const ButtonGroup = styled.div`
+ display: flex;
+ justify-content: flex-start;
+ margin-left: 168px;
+ margin-top: 30px;
+`;
+
+export const ProfileInformation = styled.div`
+ padding: 30px 20px;
+ display: flex;
+ text-align: ${left};
+ justify-content: center;
+ flex-direction: column;
+ align-items: left;
+
+ @media (max-width: 1345px) and (min-width: 1200px) {
+ padding: 30px 15px;
+ }
+
+ @media screen and (max-width: 360px) {
+ width: 100%;
+ }
+`;
+
+export const ProfileData = styled.div`
+ margin-top: 30px;
+
+ @media screen and (max-width: 360px) {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ padding: 0;
+ }
+`;
+
+export const ProfileReadOnly = styled.p`
+ flex: 1;
+ padding: 8px;
+ border: 1px solid ${colorBackgroundBody};
+ margin: 0;
+ color: ${colorText};
+ background-color: ${colorBackgroundBody};
+`;
+
+export const ProfileContact = styled.div`
+ display: flex;
+ margin-top: 10px;
+ margin-bottom: 5px;
+ line-height: 18px;
+ align-items: center;
+ input {
+ flex: 1;
+ padding: 8px;
+ border: 1px solid ${colorBackgroundBody};
+ margin: 0;
+ color: ${colorText};
+ background-color: ${colorBackground};
+ }
+`;
+
+export const Description = styled.p`
+ text-align: left;
+ margin-left: 0px;
+ margin-right: 20px;
+ width: 150px;
+`;
+
+export const ProfileIntro = styled.p`
+ font-weight: 900;
+ text-transform: uppercase;
+ text-align: left;
+ margin-bottom: 4px;
+ line-height: 18px;
+`;
+export const ProfileText = styled.p`
+ text-align: left;
+ margin-bottom: 5px;
+ line-height: 18px;
+`;
+
+export const ErrorMessage = styled.div`
+ background-color: #f8d7da;
+ color: #721c24;
+ padding: 10px;
+ margin-left: auto;
+ margin-right: auto;
+ border: 1px;
+ solid #f5c6cb;
+ border-radius: 5px;
+ text-align: center;
+`;
diff --git a/tsconfig.json b/tsconfig.json
index 79332c5..45a37de 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,52 +2,22 @@
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
- "lib": [
- "ES2020",
- "DOM",
- "DOM.Iterable"
- ],
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
- "types": [
- "vite/client",
- "jest",
- "react",
- "react-dom",
- "node"
- ],
+ "types": ["vite/client", "jest", "react", "react-dom", "node"],
"skipLibCheck": true,
"baseUrl": "src",
"paths": {
- "@/styles/*": [
- "styles/*"
- ],
- "@/config/*": [
- "config/*"
- ],
- "@/shared/*": [
- "shared/*"
- ],
- "@/graphql/*": [
- "graphql/*"
- ],
- "@/utils/*": [
- "shared/utils/*"
- ],
- "@/hooks/*": [
- "hooks/*"
- ],
- "@/constants/*": [
- "shared/constants/*"
- ],
- "@/containers/*": [
- "containers/*"
- ],
- "@/routes/*": [
- "routes/*"
- ],
- "@/components/*": [
- "shared/components/*"
- ]
+ "@/styles/*": ["styles/*"],
+ "@/config/*": ["config/*"],
+ "@/shared/*": ["shared/*"],
+ "@/graphql/*": ["graphql/*"],
+ "@/utils/*": ["shared/utils/*"],
+ "@/hooks/*": ["hooks/*"],
+ "@/constants/*": ["shared/constants/*"],
+ "@/containers/*": ["containers/*"],
+ "@/routes/*": ["routes/*"],
+ "@/components/*": ["shared/components/*"]
},
/* Bundler mode */
"moduleResolution": "bundler",
@@ -73,13 +43,6 @@
}
]
},
- "include": [
- "./src",
- "./dist/types/**/*.ts",
- "./next-env.d.ts",
- ".next/types/**/*.ts"
- ],
- "exclude": [
- "./node_modules"
- ]
+ "include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts", ".next/types/**/*.ts"],
+ "exclude": ["./node_modules"]
}