Skip to content

Commit 62c4661

Browse files
authored
fix: contact and privacy (#19)
1 parent 6e3876c commit 62c4661

34 files changed

+1827
-154
lines changed

app/contact/page.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { contactPageConfig } from '@/lib/config/pages';
33
import '@/styles/contact.scss';
4+
import Form from '@/components/contact/form';
45

56
function Page() {
67
return (
@@ -20,6 +21,7 @@ function Page() {
2021
</div>
2122
</div>
2223
<div className={'desc'}>{contactPageConfig.desc}</div>
24+
<Form />
2325
<div className={'icon icon-1'}>
2426
<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 112 158' fill='none'>
2527
<path

app/globals.css

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ body {
2222
font-size: 16px;
2323
@apply bg-light-blue-gray;
2424
@apply dark:bg-night-blue;
25+
margin: 0;
2526
}
2627

2728
@media screen and (max-width: 768px) {
2829
body {
2930
font-size: 14px;
3031
}
31-
}
32+
}

app/privacy/page.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { privacyHTML } from '@/lib/config/privacy';
2+
3+
function Page() {
4+
return (
5+
<div className={'privacy-policy'}>
6+
<div
7+
dangerouslySetInnerHTML={{
8+
__html: privacyHTML,
9+
}}
10+
></div>
11+
</div>
12+
);
13+
}
14+
15+
export default Page;

app/terms/page.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { termsHTML } from '@/lib/config/terms';
2+
3+
function Page() {
4+
return (
5+
<div className={'terms'}>
6+
<div
7+
dangerouslySetInnerHTML={{
8+
__html: termsHTML,
9+
}}
10+
></div>
11+
</div>
12+
);
13+
}
14+
15+
export default Page;

app/what-is-new/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import '@/styles/what-is-new.scss';
22
import Index from '@/components/what-is-new';
3-
import { loadVersions } from '@/lib/api';
3+
import { loadVersions } from '@/lib/githubAPI';
44

55
async function Page() {
66
const versions = await getData();
+41-28
Loading

components/banner/banner.hooks.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useCallback, useEffect } from 'react';
22
import { Storage } from '@/lib/storage';
3-
import { getLatestVersion } from '@/lib/api';
3+
import { getLatestVersion } from '@/lib/githubAPI';
44
import dayjs from 'dayjs';
55
import { useRouter } from 'next/navigation';
66
import { storageDownloadLinks } from '@/lib/hooks/use-download';

components/contact/form.tsx

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
'use client';
2+
3+
import React, { useMemo, useState } from 'react';
4+
import { useForm } from 'react-hook-form';
5+
import { createHubSpotTicket } from '@/lib/hubspotAPI';
6+
import { CircularProgress, Snackbar } from '@mui/material';
7+
import Checkbox from '@/components/icons/checkbox';
8+
9+
function Form() {
10+
const [isLoading, setIsLoading] = useState(false);
11+
const {
12+
register,
13+
handleSubmit,
14+
formState: { errors },
15+
trigger,
16+
} = useForm<{
17+
firstName: string;
18+
lastName: string;
19+
email: string;
20+
company?: string;
21+
message: string;
22+
}>({
23+
mode: 'onChange',
24+
});
25+
26+
const [message, setMessage] = useState({
27+
open: false,
28+
type: '',
29+
});
30+
const fields: {
31+
name: 'firstName' | 'lastName' | 'email' | 'company' | 'message';
32+
label: string;
33+
type: 'text' | 'email' | 'textarea';
34+
required?: boolean;
35+
}[] = useMemo(() => {
36+
return [
37+
{
38+
name: 'firstName',
39+
label: 'First name',
40+
type: 'text',
41+
required: true,
42+
},
43+
{
44+
name: 'lastName',
45+
label: 'Last name',
46+
type: 'text',
47+
required: true,
48+
},
49+
{
50+
name: 'email',
51+
label: 'Business email',
52+
type: 'email',
53+
required: true,
54+
},
55+
{
56+
name: 'company',
57+
label: 'Company name',
58+
type: 'text',
59+
},
60+
{
61+
name: 'message',
62+
label: 'Message or question',
63+
type: 'textarea',
64+
required: true,
65+
},
66+
];
67+
}, []);
68+
69+
const closeMessage = () => {
70+
setMessage({
71+
open: false,
72+
type: '',
73+
});
74+
};
75+
76+
return (
77+
<form
78+
id={'contact-form'}
79+
onSubmit={handleSubmit(async (formData) => {
80+
setIsLoading(true);
81+
try {
82+
await createHubSpotTicket(formData);
83+
setMessage({
84+
open: true,
85+
type: 'success',
86+
});
87+
} catch (error) {
88+
setMessage({
89+
open: true,
90+
type: 'error',
91+
});
92+
}
93+
94+
setIsLoading(false);
95+
})}
96+
>
97+
{fields.map((field) => {
98+
return (
99+
<div key={field.name} className={'form-group'}>
100+
<div className={'form-input'}>
101+
{field.type === 'textarea' ? (
102+
<textarea
103+
id={field.name}
104+
rows={5}
105+
placeholder={field.label}
106+
{...register(field.name, { required: field.required })}
107+
></textarea>
108+
) : (
109+
<input
110+
type={field.type}
111+
id={field.name}
112+
placeholder={field.label}
113+
{...register(field.name, {
114+
required: field.required,
115+
pattern: field.type === 'email' ? /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ : undefined,
116+
})}
117+
/>
118+
)}
119+
120+
{field.required && (
121+
<div className={'required'}>
122+
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='15' viewBox='0 0 14 15' fill='none'>
123+
<path
124+
fillRule='evenodd'
125+
clipRule='evenodd'
126+
d='M7.875 0.5H6.125V5.38786L2.66914 1.932L1.4317 3.16943L4.88727 6.625H0V8.375H4.88871L1.43157 11.8321L2.66901 13.0696L6.125 9.61359V14.5H7.875V9.61273L11.3312 13.0689L12.5686 11.8315L9.11214 8.375H14V6.625H9.11359L12.5685 3.17009L11.3311 1.93265L7.875 5.38871V0.5Z'
127+
fill='#9327FF'
128+
/>
129+
</svg>
130+
</div>
131+
)}
132+
</div>
133+
134+
{/* errors will return when field validation fails */}
135+
{errors[field.name]?.type === 'required' && <div className={'error'}>{field.label} is required</div>}
136+
{errors[field.name]?.type === 'pattern' && <div className={'error'}>{field.label} is invalid</div>}
137+
</div>
138+
);
139+
})}
140+
141+
<button
142+
onClick={() => {
143+
trigger('firstName');
144+
}}
145+
className={'download-btn'}
146+
type={'submit'}
147+
disabled={isLoading}
148+
>
149+
{isLoading && <CircularProgress size={24} color='secondary' />}
150+
<span className={'mr-1'}>Send</span>
151+
<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'>
152+
<path d='M1.5 1.5H10.5M10.5 1.5V10.5M10.5 1.5L1.5 10.5' stroke='white' strokeWidth='1.8' />
153+
</svg>
154+
</button>
155+
<Snackbar
156+
className={'contact-message'}
157+
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
158+
open={message.open}
159+
autoHideDuration={6000}
160+
onClose={closeMessage}
161+
>
162+
<div className={'contact-message-content'}>
163+
{message.type !== '' && (
164+
<>
165+
<div className={'contact-message-icon'}>
166+
{message.type === 'success' ? (
167+
<Checkbox />
168+
) : (
169+
<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 19 19' fill='none'>
170+
<path
171+
fillRule='evenodd'
172+
clipRule='evenodd'
173+
d='M9.5 1.35714C5.00311 1.35714 1.35714 5.00311 1.35714 9.5C1.35714 13.9989 5.00311 17.6429 9.5 17.6429C13.9969 17.6429 17.6429 13.9989 17.6429 9.5C17.6429 5.00311 13.9969 1.35714 9.5 1.35714ZM0 9.5C0 4.25329 4.25329 0 9.5 0C14.7467 0 19 4.25329 19 9.5C19 14.7454 14.7467 19 9.5 19C4.25329 19 0 14.7454 0 9.5ZM5.58533 5.62741C5.84998 5.36209 6.27951 5.36209 6.54483 5.62741L9.5 8.58258L12.4552 5.62741C12.7205 5.36209 13.15 5.36209 13.4147 5.62741C13.68 5.89206 13.68 6.32223 13.4147 6.58687L10.4595 9.54208L13.4147 12.4993C13.68 12.7639 13.68 13.1914 13.4147 13.4561C13.15 13.7207 12.7205 13.7207 12.4552 13.4561L9.5 10.5016L6.54483 13.4561C6.27951 13.7207 5.84998 13.7207 5.58533 13.4561C5.32001 13.1914 5.32001 12.7639 5.58533 12.4993L8.5405 9.54208L5.58533 6.58687C5.32001 6.32223 5.32001 5.89206 5.58533 5.62741Z'
174+
fill='red'
175+
/>
176+
</svg>
177+
)}
178+
</div>
179+
<div className={'contact-message-text'}>
180+
{message.type === 'success' ? (
181+
<>
182+
<div className={'contact-message-title'}>Thank you</div>
183+
<div className={'contact-message-desc'}>
184+
Your message has been sent and we will review it as soon as possible.
185+
</div>
186+
</>
187+
) : (
188+
<>
189+
<div className={'contact-message-title'}>Something went wrong</div>
190+
<div className={'contact-message-desc'}>Please try again later.</div>
191+
</>
192+
)}
193+
</div>
194+
</>
195+
)}
196+
</div>
197+
</Snackbar>
198+
</form>
199+
);
200+
}
201+
202+
export default Form;

components/contributors/contributors-list.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useLiveQuery } from 'dexie-react-hooks';
55
import { db } from '@/lib/db';
66
import { Storage } from '@/lib/storage';
77
import dayjs from 'dayjs';
8-
import { loadContributors } from '@/lib/api';
8+
import { loadContributors } from '@/lib/githubAPI';
99

1010
function ContributorsList() {
1111
const dbContributors = useLiveQuery(() => {

0 commit comments

Comments
 (0)