Skip to content

Commit feb4907

Browse files
committed
feat: added basic support for adding contacts to the contact book
1 parent ed7ff35 commit feb4907

File tree

9 files changed

+96
-13
lines changed

9 files changed

+96
-13
lines changed

bun.lock

Lines changed: 3 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
@@ -42,6 +42,7 @@
4242
"@tailwindcss/typography": "^0.5.15",
4343
"apisauce": "2.0.1",
4444
"axios": "^1.5.0",
45+
"bcryptjs": "^3.0.2",
4546
"buffer": "^6.0.3",
4647
"caniuse-lite": "^1.0.30001731",
4748
"chartjs-plugin-zoom": "^2.2.0",

src/BrightID/components/Onboarding/RecoveryFlow/RecoveryCodeScreen.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ const RecoveryCodeScreen = () => {
138138
changePrimaryDevice: false,
139139
name: `Aura ${deviceInfo}`,
140140
});
141+
141142
setQrUrl(newQrUrl);
142143
}
143144
}, [action, recoveryData.aesKey, recoveryData.channel.url]);

src/app/routes/_app.contact-info/route.tsx

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ import { Input } from '@/components/ui/input';
1212
import { Button } from '@/components/ui/button';
1313
import { Card } from '@/components/ui/card';
1414
import { Label } from '@/components/ui/label';
15-
import { Plus } from 'lucide-react';
15+
import { PhoneCall, Plus } from 'lucide-react';
1616
import DefaultHeader from '@/components/Header/DefaultHeader';
17+
import { MdEmail } from 'react-icons/md';
18+
import { useSelector } from 'react-redux';
19+
import { addContactInfo, selectContacts } from '@/store/contacts';
20+
import { useDispatch } from '@/store/hooks';
21+
import { useStoreNewContactMutation } from '@/store/api/get-verified';
1722

1823
const isValidEmail = (email: string) =>
1924
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
@@ -26,12 +31,16 @@ type Contact = {
2631
};
2732

2833
export default function ContactPage() {
29-
const [contacts, setContacts] = useState<Contact[]>([]);
3034
const [type, setType] = useState<'email' | 'phone'>('email');
3135
const [value, setValue] = useState('');
3236
const [error, setError] = useState('');
3337

34-
const handleAdd = () => {
38+
const contacts = useSelector(selectContacts);
39+
const dispatch = useDispatch();
40+
41+
const [mutate, { isLoading }] = useStoreNewContactMutation();
42+
43+
const handleAdd = async () => {
3544
if (type === 'email' && !isValidEmail(value)) {
3645
setError('Invalid email address');
3746
return;
@@ -41,9 +50,18 @@ export default function ContactPage() {
4150
return;
4251
}
4352

44-
setContacts([...contacts, { type, value }]);
45-
setValue('');
46-
setError('');
53+
mutate(value).then((res) => {
54+
if (res.error) {
55+
setError((res.error as { message: string })?.message?.toString());
56+
57+
return;
58+
}
59+
60+
dispatch(addContactInfo({ type, value }));
61+
62+
setValue('');
63+
setError('');
64+
});
4765
};
4866

4967
return (
@@ -63,19 +81,15 @@ export default function ContactPage() {
6381
className="flex items-center justify-between p-4 text-sm"
6482
>
6583
<span className="capitalize">{c.type}:</span>
66-
<span>{c.value}</span>
84+
<span>******************</span>
6785
</Card>
6886
))}
6987
</div>
7088
</div>
7189

72-
{/* Floating Action Button and Dialog */}
7390
<Dialog>
7491
<DialogTrigger asChild>
75-
<Button
76-
className="h-12 bg-primary/60 p-0 text-black hover:bg-primary/90"
77-
variant="default"
78-
>
92+
<Button className="h-12 p-0 text-black" variant="default">
7993
<Plus size={24} />
8094
</Button>
8195
</DialogTrigger>
@@ -93,6 +107,7 @@ export default function ContactPage() {
93107
setError('');
94108
}}
95109
>
110+
<MdEmail />
96111
Email
97112
</Button>
98113
<Button
@@ -102,6 +117,7 @@ export default function ContactPage() {
102117
setError('');
103118
}}
104119
>
120+
<PhoneCall />
105121
Phone
106122
</Button>
107123
</div>
@@ -124,7 +140,7 @@ export default function ContactPage() {
124140
{error && <p className="mt-1 text-xs text-red-500">{error}</p>}
125141
</div>
126142

127-
<Button onClick={handleAdd} className="w-full">
143+
<Button disabled={isLoading} onClick={handleAdd} className="w-full">
128144
Save Contact
129145
</Button>
130146
</div>

src/app/routes/_app.settings/route.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export default function Settings() {
2121
<DefaultHeader title="Settings" />
2222
<div className="page flex w-full flex-1 flex-col gap-4 pt-4 dark:text-white">
2323
<section className="mt-20 flex w-full flex-col gap-4">
24+
<Link to="/contact-info">
25+
<Card className="flex cursor-pointer items-center gap-2 rounded-lg py-3.5 pl-5 pr-2">
26+
<MdContactMail size={20} />
27+
<p className="text-[20px] font-medium">Your Contact info</p>
28+
</Card>
29+
</Link>
2430
<Card
2531
className="flex cursor-pointer items-center gap-2 rounded-lg py-3.5 pl-5 pr-2"
2632
onClick={() => navigate(RoutePath.ROLE_MANAGEMENT)}

src/constants/contacts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// This is for making the hash value harder to compute, and prevent brute forcing the hash actual value
2+
export const BASE_SALT_FOR_CONTACTS = '$2a$12$abcdefghijklmnopqrstuv';

src/store/api/get-verified.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import axios from 'axios';
2+
import { apiSlice } from './slice';
3+
import bcrypt from 'bcryptjs';
4+
import { BASE_SALT_FOR_CONTACTS } from '@/constants/contacts';
5+
6+
const salt = BASE_SALT_FOR_CONTACTS;
7+
8+
export const getVerifiedApi = apiSlice.injectEndpoints({
9+
endpoints: (build) => ({
10+
storeNewContact: build.mutation<null, string>({
11+
queryFn: async (value) => {
12+
const hashed = await bcrypt.hash(value, salt);
13+
14+
return axios.post(
15+
'https://aura-get-verified.vercel.app/api/create-social',
16+
{
17+
hash: hashed,
18+
},
19+
);
20+
},
21+
}),
22+
}),
23+
overrideExisting: true,
24+
});
25+
26+
export const { useStoreNewContactMutation } = getVerifiedApi;

src/store/contacts/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
2+
import { RootState } from '..';
3+
4+
const initialContactsState = {
5+
storedInfoHashed: [] as { type: string; value: string }[],
6+
};
7+
8+
export const contactsSlice = createSlice({
9+
name: 'contacts',
10+
initialState: initialContactsState,
11+
reducers: {
12+
addContactInfo(
13+
state,
14+
action: PayloadAction<{ type: string; value: string }>,
15+
) {
16+
state.storedInfoHashed.push(action.payload);
17+
},
18+
},
19+
});
20+
21+
export const selectContacts = createSelector(
22+
(state: RootState) => state.contacts,
23+
(contacts) => contacts.storedInfoHashed,
24+
);
25+
26+
export const { addContactInfo } = contactsSlice.actions;

src/store/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { cacheSlice } from './cache';
3030
import localforage from 'localforage';
3131
import { notificationsSlice } from './notifications';
3232
import { alertsSlice } from './notifications/slice';
33+
import { contactsSlice } from './contacts';
3334

3435
const migrationManifest = createMigrate(migrations, { debug: __DEV__ });
3536

@@ -100,6 +101,7 @@ const rootReducer = withReduxStateSync(
100101
...reducers,
101102
profile: profileSlice.reducer,
102103
[notificationsSlice.reducerPath]: notificationsSlice.reducer,
104+
[contactsSlice.reducerPath]: contactsSlice.reducer,
103105
[alertsSlice.reducerPath]: alertsSlice.reducer,
104106
[cacheSlice.reducerPath]: cacheSlice.reducer,
105107
}),

0 commit comments

Comments
 (0)