Skip to content

Commit 504f648

Browse files
reg added
1 parent cb27435 commit 504f648

10 files changed

Lines changed: 8148 additions & 950 deletions

File tree

package-lock.json

Lines changed: 5637 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@dnd-kit/utilities": "^3.2.2",
1616
"@fluentui/react-calendar-compat": "^0.3.15",
1717
"@fluentui/react-components": "^9.72.4",
18+
"@fluentui/react-datepicker-compat": "^0.6.20",
1819
"axios": "^1.13.2",
1920
"react": "19",
2021
"react-dom": "19",

src/components/apis/auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import axiosInstance from './axiosInstance';
2+
import type { AddressData } from './philippineAddress';
23

34
export interface User {
45
id: string;
@@ -7,9 +8,12 @@ export interface User {
78
firstName: string;
89
middleName: string;
910
contactNumber: string;
11+
secondaryContactNumber?: string | null;
1012
birthDate: string;
1113
userIMG: string | null;
1214
email: string;
15+
address?: AddressData | null;
16+
secondaryAddress?: AddressData | null;
1317
createdAt: string;
1418
}
1519

@@ -30,10 +34,13 @@ export interface RegisterRequest {
3034
lastName: string;
3135
middleName: string;
3236
contactNumber: string;
37+
secondaryContactNumber?: string | null;
3338
birthDate: string;
3439
userIMG: string | null;
3540
email: string;
3641
password: string;
42+
address?: AddressData | null;
43+
secondaryAddress?: AddressData | null;
3744
}
3845

3946
export interface RegisterResponse {

src/components/apis/axiosInstance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import axios from 'axios';
22

33
// export const API_BASE_URL = "https://flowboard-backend.azurewebsites.net/";
4-
export const API_BASE_URL = "https://animated-space-fiesta-w6wpx564wqxh5vwj-5158.app.github.dev//";
5-
// export const API_BASE_URL = "http://localhost:5158/";
4+
// export const API_BASE_URL = "https://animated-space-fiesta-w6wpx564wqxh5vwj-5158.app.github.dev//";
5+
export const API_BASE_URL = "http://localhost:5158/";
66

77
const axiosInstance = axios.create({
88
baseURL: API_BASE_URL,

src/components/apis/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export * from './tasks';
33
export * from './users';
44
export * from './projects';
55
export * from './categories';
6+
export * from './philippineAddress';
67
export { default as axiosInstance } from './axiosInstance';
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Philippine Standard Geographic Code (PSGC) API
2+
// Using the free PSGC API from https://psgc.gitlab.io/api/
3+
4+
const PSGC_BASE_URL = 'https://psgc.gitlab.io/api';
5+
6+
export interface Region {
7+
code: string;
8+
name: string;
9+
regionName: string;
10+
islandGroupCode: string;
11+
psgc10DigitCode: string;
12+
}
13+
14+
export interface Province {
15+
code: string;
16+
name: string;
17+
regionCode: string;
18+
islandGroupCode: string;
19+
psgc10DigitCode: string;
20+
}
21+
22+
export interface CityMunicipality {
23+
code: string;
24+
name: string;
25+
oldName?: string;
26+
isCapital: boolean;
27+
isCity: boolean;
28+
isMunicipality: boolean;
29+
districtCode?: string;
30+
provinceCode?: string;
31+
regionCode: string;
32+
islandGroupCode: string;
33+
psgc10DigitCode: string;
34+
}
35+
36+
export interface Barangay {
37+
code: string;
38+
name: string;
39+
oldName?: string;
40+
subMunicipalityCode?: string;
41+
cityCode?: string;
42+
municipalityCode?: string;
43+
districtCode?: string;
44+
provinceCode?: string;
45+
regionCode: string;
46+
islandGroupCode: string;
47+
psgc10DigitCode: string;
48+
}
49+
50+
export interface AddressData {
51+
region: string;
52+
regionCode: string;
53+
province: string;
54+
provinceCode: string;
55+
cityMunicipality: string;
56+
cityMunicipalityCode: string;
57+
barangay: string;
58+
barangayCode: string;
59+
streetAddress: string;
60+
zipCode: string;
61+
}
62+
63+
export const philippineAddressApi = {
64+
/**
65+
* Get all regions
66+
*/
67+
getRegions: async (): Promise<Region[]> => {
68+
try {
69+
const response = await fetch(`${PSGC_BASE_URL}/regions/`);
70+
if (!response.ok) throw new Error('Failed to fetch regions');
71+
return await response.json();
72+
} catch (error) {
73+
console.error('Error fetching regions:', error);
74+
throw error;
75+
}
76+
},
77+
78+
/**
79+
* Get all provinces for a region
80+
*/
81+
getProvincesByRegion: async (regionCode: string): Promise<Province[]> => {
82+
try {
83+
const response = await fetch(`${PSGC_BASE_URL}/regions/${regionCode}/provinces/`);
84+
if (!response.ok) throw new Error('Failed to fetch provinces');
85+
return await response.json();
86+
} catch (error) {
87+
console.error('Error fetching provinces:', error);
88+
throw error;
89+
}
90+
},
91+
92+
/**
93+
* Get all cities/municipalities for a province
94+
*/
95+
getCitiesMunicipalitiesByProvince: async (provinceCode: string): Promise<CityMunicipality[]> => {
96+
try {
97+
const response = await fetch(`${PSGC_BASE_URL}/provinces/${provinceCode}/cities-municipalities/`);
98+
if (!response.ok) throw new Error('Failed to fetch cities/municipalities');
99+
return await response.json();
100+
} catch (error) {
101+
console.error('Error fetching cities/municipalities:', error);
102+
throw error;
103+
}
104+
},
105+
106+
/**
107+
* Get cities/municipalities directly under a region (for NCR, which has no provinces)
108+
*/
109+
getCitiesMunicipalitiesByRegion: async (regionCode: string): Promise<CityMunicipality[]> => {
110+
try {
111+
const response = await fetch(`${PSGC_BASE_URL}/regions/${regionCode}/cities-municipalities/`);
112+
if (!response.ok) throw new Error('Failed to fetch cities/municipalities');
113+
return await response.json();
114+
} catch (error) {
115+
console.error('Error fetching cities/municipalities:', error);
116+
throw error;
117+
}
118+
},
119+
120+
/**
121+
* Get all barangays for a city/municipality
122+
*/
123+
getBarangaysByCityMunicipality: async (cityMunicipalityCode: string): Promise<Barangay[]> => {
124+
try {
125+
// Try city endpoint first
126+
let response = await fetch(`${PSGC_BASE_URL}/cities/${cityMunicipalityCode}/barangays/`);
127+
128+
if (!response.ok) {
129+
// Try municipality endpoint if city fails
130+
response = await fetch(`${PSGC_BASE_URL}/municipalities/${cityMunicipalityCode}/barangays/`);
131+
}
132+
133+
if (!response.ok) throw new Error('Failed to fetch barangays');
134+
return await response.json();
135+
} catch (error) {
136+
console.error('Error fetching barangays:', error);
137+
throw error;
138+
}
139+
},
140+
141+
/**
142+
* Get districts for NCR (special case)
143+
*/
144+
getDistrictsByRegion: async (regionCode: string): Promise<Province[]> => {
145+
try {
146+
const response = await fetch(`${PSGC_BASE_URL}/regions/${regionCode}/districts/`);
147+
if (!response.ok) throw new Error('Failed to fetch districts');
148+
return await response.json();
149+
} catch (error) {
150+
console.error('Error fetching districts:', error);
151+
throw error;
152+
}
153+
},
154+
155+
/**
156+
* Check if a region is NCR (National Capital Region)
157+
* NCR has districts instead of provinces
158+
*/
159+
isNCR: (regionCode: string): boolean => {
160+
return regionCode === '130000000'; // NCR code
161+
},
162+
};
163+
164+
export const emptyAddressData: AddressData = {
165+
region: '',
166+
regionCode: '',
167+
province: '',
168+
provinceCode: '',
169+
cityMunicipality: '',
170+
cityMunicipalityCode: '',
171+
barangay: '',
172+
barangayCode: '',
173+
streetAddress: '',
174+
zipCode: '',
175+
};

src/components/auth/Login.tsx

Lines changed: 105 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react';
2-
import { Button, Input, Label, tokens, Card, Text, mergeClasses } from '@fluentui/react-components';
2+
import { Button, Input, Label, tokens, Card, Text } from '@fluentui/react-components';
3+
import { PersonRegular, LockClosedRegular } from '@fluentui/react-icons';
34
import { useNavigate } from 'react-router';
45
import { useForm } from 'react-hook-form';
56
import { mainLayoutStyles } from '../styles/Styles';
@@ -37,51 +38,114 @@ export default function Login() {
3738
};
3839

3940
return (
40-
<Card className={mergeClasses(styles.layoutPadding, styles.flexColFit, styles.componentBorder)} style={{ width: '55%' }}>
41-
<h2 className={styles.brand}>Sign in</h2>
42-
<form className={styles.formSection} onSubmit={handleSubmit(onSubmit)}>
43-
<div className={styles.formField}>
44-
<Label htmlFor="userNameOrEmail">Username or Email</Label>
45-
<Input
46-
className={styles.formField}
47-
id="userNameOrEmail"
48-
type="text"
49-
placeholder="username or you@email.com"
50-
autoComplete="username"
51-
{...register('userNameOrEmail', { required: 'Username or Email is required' })}
52-
/>
53-
{errors.userNameOrEmail && (
54-
<Text as="span" className={styles.errorText}>{errors.userNameOrEmail.message}</Text>
55-
)}
56-
</div>
57-
<div className={styles.formField}>
58-
<Label htmlFor="password">Password</Label>
59-
<Input
60-
className={styles.formField}
61-
id="password"
62-
type="password"
63-
placeholder="Password"
64-
autoComplete="current-password"
65-
{...register('password', { required: 'Password is required' })}
66-
/>
67-
{errors.password && (
68-
<Text as="span" className={styles.errorText}>{errors.password.message}</Text>
41+
<Card style={{
42+
width: '100%',
43+
maxWidth: '520px',
44+
margin: '0 auto',
45+
padding: `${tokens.spacingVerticalXXL} ${tokens.spacingHorizontalXXXL}`
46+
}}>
47+
{/* Header - Visual Hierarchy (HCI Principle) */}
48+
<div style={{ marginBottom: tokens.spacingVerticalXXL, textAlign: 'center' }}>
49+
<Text size={800} weight="semibold" block style={{ marginBottom: tokens.spacingVerticalM }}>
50+
Welcome back
51+
</Text>
52+
<Text size={400} style={{ color: tokens.colorNeutralForeground3 }}>
53+
Sign in to continue to Flowboard
54+
</Text>
55+
</div>
56+
57+
<form onSubmit={handleSubmit(onSubmit)}>
58+
{/* Adequate spacing for readability and reduced cognitive load */}
59+
<div style={{ display: 'flex', flexDirection: 'column', gap: tokens.spacingVerticalL }}>
60+
<div className={styles.formField}>
61+
<Label htmlFor="userNameOrEmail" required size="medium" style={{ marginBottom: tokens.spacingVerticalXS }}>
62+
Username or Email
63+
</Label>
64+
<Input
65+
id="userNameOrEmail"
66+
type="text"
67+
placeholder="Enter your username or email"
68+
autoComplete="username"
69+
size="large"
70+
contentBefore={<PersonRegular />}
71+
style={{ width: '100%' }}
72+
{...register('userNameOrEmail', { required: 'Username or Email is required' })}
73+
/>
74+
{errors.userNameOrEmail && (
75+
<Text className={styles.errorText} style={{ marginTop: tokens.spacingVerticalXS }}>
76+
{errors.userNameOrEmail.message}
77+
</Text>
78+
)}
79+
</div>
80+
81+
<div className={styles.formField}>
82+
<Label htmlFor="password" required size="medium" style={{ marginBottom: tokens.spacingVerticalXS }}>
83+
Password
84+
</Label>
85+
<Input
86+
id="password"
87+
type="password"
88+
placeholder="Enter your password"
89+
autoComplete="current-password"
90+
size="large"
91+
contentBefore={<LockClosedRegular />}
92+
style={{ width: '100%' }}
93+
{...register('password', { required: 'Password is required' })}
94+
/>
95+
{errors.password && (
96+
<Text className={styles.errorText} style={{ marginTop: tokens.spacingVerticalXS }}>
97+
{errors.password.message}
98+
</Text>
99+
)}
100+
</div>
101+
102+
{formError && (
103+
<Text className={styles.errorText} style={{ display: 'block', textAlign: 'center' }}>
104+
{formError}
105+
</Text>
69106
)}
70-
</div>
71-
{formError && (
72-
<Text as="span" className={styles.errorText} style={{ marginTop: tokens.spacingVerticalXS }}>
73-
{formError}
74-
</Text>
75-
)}
76-
<div className={styles.formSection}>
77-
<Button className={styles.formField} appearance="primary" type="submit" size="large" disabled={loading}>
107+
108+
{/* Fitts's Law - Large clickable target for primary action */}
109+
<Button
110+
appearance="primary"
111+
type="submit"
112+
size="large"
113+
disabled={loading}
114+
style={{
115+
width: '100%',
116+
marginTop: tokens.spacingVerticalM,
117+
padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalL}`
118+
}}
119+
>
78120
{loading ? 'Signing In...' : 'Sign In'}
79121
</Button>
80-
<Button className={styles.formField} appearance="outline" size="large" onClick={() => { navigate("/register") }} type="button">
81-
Create Account
82-
</Button>
83122
</div>
84123
</form>
124+
125+
{/* Footer - Clear visual separation and secondary action */}
126+
<div style={{
127+
marginTop: tokens.spacingVerticalXXL,
128+
paddingTop: tokens.spacingVerticalL,
129+
borderTop: `1px solid ${tokens.colorNeutralStroke2}`,
130+
textAlign: 'center',
131+
display: 'flex',
132+
alignItems: 'center',
133+
justifyContent: 'center',
134+
gap: tokens.spacingHorizontalXS
135+
}}>
136+
<Text size={400} style={{ color: tokens.colorNeutralForeground3 }}>
137+
Don't have an account?
138+
</Text>
139+
<Button
140+
appearance="transparent"
141+
size="medium"
142+
onClick={() => navigate('/register')}
143+
type="button"
144+
style={{ padding: `0 ${tokens.spacingHorizontalXS}`, minWidth: 'auto', fontWeight: 600 }}
145+
>
146+
Create one
147+
</Button>
148+
</div>
85149
</Card>
86150
);
87151
}

0 commit comments

Comments
 (0)