Skip to content

Commit

Permalink
Feat: onboarding data privacy (#48)
Browse files Browse the repository at this point in the history
* Data and Privacy Screen and Privacy Policy Screen. Onboarding Top Menu also.
  • Loading branch information
cimigree authored Dec 3, 2024
1 parent 732f43d commit bacd1b2
Show file tree
Hide file tree
Showing 25 changed files with 1,272 additions and 205 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/data
/messages
/translations
src/renderer/src/routeTree.gen.ts
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export default tseslint.config(
ecmaVersion: 2022,
sourceType: 'module',
},
ignores: ['src/renderer/src/routeTree.gen.ts'],
},
// Global ignores
includeIgnoreFile(fileURLToPath(new URL('.gitignore', import.meta.url))),
Expand Down
144 changes: 144 additions & 0 deletions messages/renderer/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
{
"components.OnboardingTopMenu.goBack": {
"message": "Go back"
},
"components.OnboardingTopMenu.step": {
"message": "Step {number}"
},
"screens.DataPrivacy.description": {
"message": "CoMapeo allows teams to map offline without needing internet servers."
},
"screens.DataPrivacy.diagnostic": {
"message": "Private by default — diagnostic information is made fully anonymous and you can opt-out any time."
},
"screens.DataPrivacy.encrypted": {
"message": "All data stays fully encrypted"
},
"screens.DataPrivacy.learnMore": {
"message": "Learn More"
},
"screens.DataPrivacy.manageAndControl": {
"message": "Easily manage and control sharing and collaboration."
},
"screens.DataPrivacy.next": {
"message": "Next"
},
"screens.DataPrivacy.stays": {
"message": "Your data stays on your devices."
},
"screens.DataPrivacy.title": {
"message": "Review Data & Privacy"
},
"screens.IntroToCoMapeo.collaborate": {
"message": "Collaborate with others"
},
Expand All @@ -16,5 +46,119 @@
},
"screens.IntroToCoMapeo.viewAndManage": {
"message": "View and manage observations in CoMapeo Mobile Projects."
},
"screens.OnboardingPrivacyPolicy.permissionsTitle": {
"message": "Current Permissions"
},
"screens.PrivacyPolicy.aboutAwana": {
"message": "About Awana Digital"
},
"screens.PrivacyPolicy.aboutAwanaContent": {
"message": "CoMapeo is developed by Awana Digital, a 501c3 non-profit organization registered in the United States. Awana Digital works in solidarity with frontline communities to use technology to defend their rights and fight climate change."
},
"screens.PrivacyPolicy.appErrors": {
"message": "App Errors"
},
"screens.PrivacyPolicy.appErrorsDescription": {
"message": "Information about internal errors that result in the app not functioning as expected"
},
"screens.PrivacyPolicy.appInfo": {
"message": "App Info"
},
"screens.PrivacyPolicy.appInfoDescription": {
"message": "The version and locale (language) of CoMapeo"
},
"screens.PrivacyPolicy.appUsageTitle": {
"message": "App Usage"
},
"screens.PrivacyPolicy.comapeoPrivacyPolicy": {
"message": "CoMapeo Data Privacy"
},
"screens.PrivacyPolicy.control": {
"message": "You're in control"
},
"screens.PrivacyPolicy.controlDescription": {
"message": "You can opt out of sending any information to Awana Digital. You choose where your data is stored and who it is shared with. You may choose to share anonymized, summarized data about how you use CoMapeo with Awana Digital. We will always be transparent about what information you choose to share for the purposed of improving the app, and this information will never include photos, videos, audio, text, or precise locations that you have entered into CoMapeo."
},
"screens.PrivacyPolicy.crashData": {
"message": "Crash Data"
},
"screens.PrivacyPolicy.crashDataDescription": {
"message": "Information about what caused the app to close unexpectedly"
},
"screens.PrivacyPolicy.dataCollection": {
"message": "CoMapeo Data Collection"
},
"screens.PrivacyPolicy.deviceInfo": {
"message": "Device Info"
},
"screens.PrivacyPolicy.deviceInfoDescription": {
"message": "Such as model and manufacturer of your device; device operating system; screen size; device locale (language); device memory."
},
"screens.PrivacyPolicy.diagnosticsTitle": {
"message": "Diagnostics"
},
"screens.PrivacyPolicy.noPII": {
"message": "No personally identifiable information"
},
"screens.PrivacyPolicy.noPIIDescription": {
"message": "Using CoMapeo does not require a user account. Awana Digital does not collect your name, email address or any other personal details. No permanent user identifier or device identifier is ever shared with Awana Digital, and we take extra measures to ensure that no information you share can be used to track you: identifiers are randomized and rotated (changed) every month and we do not store IP addresses."
},
"screens.PrivacyPolicy.notCollected": {
"message": "What is not collected?"
},
"screens.PrivacyPolicy.notCollectedDescription": {
"message": "We do not collect any personal data or anything that can be used to identify or track a user or device. Device identifiers used to aggregate information are random, anonymous, and changed every month. Diagnostic information does not include data about how you use the app, and it never includes any of the data you have collected with the app. We do not collect your precise or coarse location, only the country where you are using CoMapeo."
},
"screens.PrivacyPolicy.openSource": {
"message": "Open Source and the \"Official\" Version"
},
"screens.PrivacyPolicy.openSourceContent": {
"message": "CoMapeo is an open-source application. This means that anyone can view the code that makes the app work and can verify the privacy declarations in this document. It also means that anyone can adapt the app to their own needs and release an alternative version. This document refers to data collected by the official releases of CoMapeo, digitally signed by Awana Digital, available from the Google Play Store or the Awana Digital website. Unofficial releases of CoMapeo obtained from other channels are outside our control and may share additional information with other organizations."
},
"screens.PrivacyPolicy.overview": {
"message": "The following explains what information (or “data”) is sent from CoMapeo to the application developer, Awana Digital, and how that information is used."
},
"screens.PrivacyPolicy.performanceData": {
"message": "Performance Data"
},
"screens.PrivacyPolicy.performanceDataDescription": {
"message": "Such as launch time, energy usage, app freezes, and responsiveness"
},
"screens.PrivacyPolicy.privacyPolicyTitle": {
"message": "Privacy Policy"
},
"screens.PrivacyPolicy.privateByDefault": {
"message": "Private by default"
},
"screens.PrivacyPolicy.privateByDefaultDescription": {
"message": "The data you collect and create with CoMapeo (locations, photos, video, audio, text) is only stored on your device by default, and is not stored or sent anywhere else. When you share data with collaborators by joining a project with them, it is sent encrypted, directly to your collaborators' device. This means that the data is not sent via Awana Digital, nor anyone else, on its way to your collaborator. Awana Digital never sees nor has access to any of the data you collect with CoMapeo unless you explicitly send it to us."
},
"screens.PrivacyPolicy.shareDiagnostics": {
"message": "Share Diagnostic Information"
},
"screens.PrivacyPolicy.thirdParty": {
"message": "Third-party access to data"
},
"screens.PrivacyPolicy.thirdPartyDescription": {
"message": "A \"third-party\" is an organization other than Awana Digital."
},
"screens.PrivacyPolicy.userCount": {
"message": "User Counts"
},
"screens.PrivacyPolicy.userCountDescription": {
"message": "The number of users per country and per project. Aggregated and anonymized"
},
"screens.PrivacyPolicy.whatIsCollected": {
"message": "What is collected?"
},
"screens.PrivacyPolicy.whatIsCollectedDescription": {
"message": "By default, anonymized diagnostic information about your device, app crashes, errors and performance is shared with Awana Digital. You can opt-out of sharing this information at any time. This diagnostic information is completely anonymized and it never contains any of your data (the data you have collected with CoMapeo)."
},
"screens.PrivacyPolicy.whyCollected": {
"message": "Why is this data collected?"
},
"screens.PrivacyPolicy.whyCollectedDescription": {
"message": "Crash data and app errors together with the device and app info provide Awana Digital with the information we need to fix errors and bugs in the app. The performance data helps us improve the responsiveness of the app and identify errors. User counts, including total users, users per country, and users per project, help justify ongoing investment in the development of CoMapeo."
}
}
11 changes: 7 additions & 4 deletions src/renderer/src/Theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,20 @@ const theme = createTheme({
styleOverrides: {
root: ({ ownerState }) => ({
...(ownerState.variant === 'outlined' && {
borderColor: '#EEEEEE',
borderWidth: 2,
borderColor: '#CCCCD6',
borderWidth: 1,
'&:hover': {
borderWidth: 2,
borderWidth: 1,
},
'&:focus': {
borderWidth: 2,
borderWidth: 1,
},
}),
borderRadius: 4,
textTransform: 'none',
fontSize: '1rem',
fontWeight: 400,
paddingVertical: 12,
}),
},
},
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/src/colors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
export const COMAPEO_BLUE = '#0066FF'
export const DARK_COMAPEO_BLUE = '#050F77'
export const LIGHT_COMAPEO_BLUE = '#CCE0FF'

export const CORNFLOWER_BLUE = '#80A0FF'
export const BLUE_GREY = '#CCCCD6'
export const DARK_GREY = '#757575'
export const VERY_LIGHT_GREY = '#ededed'

export const ORANGE = '#FF9933'
export const DARK_ORANGE = '#E86826'
Expand Down
119 changes: 119 additions & 0 deletions src/renderer/src/components/OnboardingTopMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { styled } from '@mui/material/styles'
import { useNavigate, useRouter } from '@tanstack/react-router'
import { defineMessages, useIntl } from 'react-intl'

import { BLACK, BLUE_GREY, COMAPEO_BLUE, WHITE } from '../colors'
import { Button } from './Button'
import { Text } from './Text'

const MenuContainer = styled('div')({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '55%',
margin: '16px auto',
position: 'relative',
})
const GoBackButton = styled(Button)({
display: 'flex',
alignItems: 'center',
gap: 8,
backgroundColor: 'transparent',
color: BLUE_GREY,
fontSize: 16,
padding: '12px 32px',
borderRadius: 20,
whiteSpace: 'nowrap',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
},
})
const BackArrow = styled('span')({
fontSize: 24,
color: WHITE,
})
const StepsContainer = styled('div')({
display: 'flex',
alignItems: 'center',
})
const Steps = styled('div')({
display: 'flex',
alignItems: 'center',
gap: 16,
})
const Step = styled('div')(({ active }: { active: boolean }) => ({
backgroundColor: active ? WHITE : 'transparent',
color: active ? BLACK : BLUE_GREY,
padding: '12px 32px',
borderRadius: 20,
fontWeight: active ? 'bold' : 'normal',
whiteSpace: 'nowrap',
}))
const Divider = styled('div')({
width: 16,
height: 1,
backgroundColor: COMAPEO_BLUE,
alignSelf: 'center',
margin: '0 12px',
})

interface OnboardingTopMenuProps {
currentStep: number
}

const m = defineMessages({
goBack: {
id: 'components.OnboardingTopMenu.goBack',
defaultMessage: 'Go back',
},
step: {
id: 'components.OnboardingTopMenu.step',
defaultMessage: 'Step {number}',
},
})

export function OnboardingTopMenu({ currentStep }: OnboardingTopMenuProps) {
const navigate = useNavigate()
const router = useRouter()
const { formatMessage } = useIntl()
const stepToRoute: Record<number, string> = {
1: 'DataPrivacy',
2: 'NextStep',
3: 'PrivacyPolicyScreen',
}

return (
<MenuContainer>
<GoBackButton
onClick={() => router.history.back()}
variant="text"
style={{
color: BLUE_GREY,
gap: 8,
padding: '12px 32px',
}}
aria-label={formatMessage(m.goBack)}
>
<BackArrow aria-hidden="true"></BackArrow>
{formatMessage(m.goBack)}
</GoBackButton>
<Steps>
{[1, 2, 3].map((step) => (
<StepsContainer key={step}>
<Step
active={currentStep === step}
onClick={() =>
navigate({ to: `/Onboarding/${stepToRoute[step]}` })
}
>
<Text kind="body" bold={currentStep === step}>
{formatMessage(m.step, { number: step })}
</Text>
</Step>
{step < 3 && <Divider />}
</StepsContainer>
))}
</Steps>
</MenuContainer>
)
}
42 changes: 42 additions & 0 deletions src/renderer/src/components/PrivacyPolicy/DiagnosticItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import CircleIcon from '@mui/icons-material/Circle'
import { styled } from '@mui/material/styles'

import { DARK_GREY } from '../../colors'
import { Text } from '../../components/Text'

const DiagnosticsItem = styled('div')({
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
gap: 8,
marginBottom: 12,
paddingLeft: 12,
})
const DiagnosticsTitle = styled(Text)({
fontWeight: 'bold',
marginRight: 4,
color: DARK_GREY,
lineHeight: 1,
})
const DiagnosticsDescription = styled(Text)({
display: 'inline',
})

export const DiagnosticItem = ({
title,
description,
}: {
title: string
description: string
}) => (
<DiagnosticsItem>
<CircleIcon
fontSize="small"
sx={{ fontSize: 6, color: DARK_GREY, marginTop: 1 }}
/>
<DiagnosticsTitle kind="subtitle">
{title}:{' '}
<DiagnosticsDescription kind="body">{description}</DiagnosticsDescription>
</DiagnosticsTitle>
</DiagnosticsItem>
)
Loading

0 comments on commit bacd1b2

Please sign in to comment.