diff --git a/.prettierignore b/.prettierignore index 1807c4b..91e6c9a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ /data /messages /translations +src/renderer/src/routeTree.gen.ts diff --git a/eslint.config.js b/eslint.config.js index a52025c..a41e72e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -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))), diff --git a/messages/renderer/en.json b/messages/renderer/en.json index 839b3dc..2ecee9c 100644 --- a/messages/renderer/en.json +++ b/messages/renderer/en.json @@ -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" }, @@ -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." } } diff --git a/src/renderer/src/Theme.ts b/src/renderer/src/Theme.ts index f6f0c8b..5201cbb 100644 --- a/src/renderer/src/Theme.ts +++ b/src/renderer/src/Theme.ts @@ -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, }), }, }, diff --git a/src/renderer/src/colors.ts b/src/renderer/src/colors.ts index 9476147..fad1f5a 100644 --- a/src/renderer/src/colors.ts +++ b/src/renderer/src/colors.ts @@ -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' diff --git a/src/renderer/src/components/OnboardingTopMenu.tsx b/src/renderer/src/components/OnboardingTopMenu.tsx new file mode 100644 index 0000000..4515f8e --- /dev/null +++ b/src/renderer/src/components/OnboardingTopMenu.tsx @@ -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 = { + 1: 'DataPrivacy', + 2: 'NextStep', + 3: 'PrivacyPolicyScreen', + } + + return ( + + router.history.back()} + variant="text" + style={{ + color: BLUE_GREY, + gap: 8, + padding: '12px 32px', + }} + aria-label={formatMessage(m.goBack)} + > + + {formatMessage(m.goBack)} + + + {[1, 2, 3].map((step) => ( + + + navigate({ to: `/Onboarding/${stepToRoute[step]}` }) + } + > + + {formatMessage(m.step, { number: step })} + + + {step < 3 && } + + ))} + + + ) +} diff --git a/src/renderer/src/components/PrivacyPolicy/DiagnosticItem.tsx b/src/renderer/src/components/PrivacyPolicy/DiagnosticItem.tsx new file mode 100644 index 0000000..332524b --- /dev/null +++ b/src/renderer/src/components/PrivacyPolicy/DiagnosticItem.tsx @@ -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 +}) => ( + + + + {title}:{' '} + {description} + + +) diff --git a/src/renderer/src/components/PrivacyPolicy/MetricsDiagnosticsPermissionToggle.tsx b/src/renderer/src/components/PrivacyPolicy/MetricsDiagnosticsPermissionToggle.tsx new file mode 100644 index 0000000..6320a00 --- /dev/null +++ b/src/renderer/src/components/PrivacyPolicy/MetricsDiagnosticsPermissionToggle.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import Checkbox from '@mui/material/Checkbox' +import { styled } from '@mui/material/styles' +import { defineMessages, useIntl } from 'react-intl' + +import { BLACK } from '../../colors' + +const m = defineMessages({ + shareDiagnostics: { + id: 'screens.PrivacyPolicy.shareDiagnostics', + defaultMessage: 'Share Diagnostic Information', + }, +}) + +const Container = styled('div')({ + display: 'flex', + alignItems: 'center', +}) + +const PermissionText = styled('span')({ + fontSize: 16, + color: BLACK, + flex: 1, +}) + +export const MetricsDiagnosticsPermissionToggle = () => { + const { formatMessage } = useIntl() + const [isEnabled, setIsEnabled] = React.useState(() => { + const savedValue = localStorage.getItem('MetricDiagnosticsPermission') + return savedValue !== null ? savedValue === 'true' : true + }) + + const togglePermission = () => { + const newValue = !isEnabled + setIsEnabled(newValue) + // TODO replace code once the Metrics and Diagnostics handling code is incorporated + localStorage.setItem('MetricDiagnosticsPermission', String(newValue)) + + console.log('Permission updated:', newValue) + } + + return ( + + {formatMessage(m.shareDiagnostics)} + + + ) +} diff --git a/src/renderer/src/components/PrivacyPolicy/PointContainer.tsx b/src/renderer/src/components/PrivacyPolicy/PointContainer.tsx new file mode 100644 index 0000000..c10eb27 --- /dev/null +++ b/src/renderer/src/components/PrivacyPolicy/PointContainer.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import { styled } from '@mui/material/styles' + +import { BLACK, DARK_GREY } from '../../colors' +import { Text } from '../Text' + +const Container = styled('div')({ + gap: 20, + paddingBottom: 20, + displau: 'flex', +}) + +const PointHeader = styled('div')({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 10, +}) + +const PointTitle = styled(Text)({ + fontSize: 16, + color: BLACK, +}) + +export function PointContainer({ + icon, + title, + description, +}: { + icon: React.ComponentType> | string + title: string + description: string +}) { + const isSvg = typeof icon !== 'string' + return ( + + + {isSvg && icon ? ( + React.createElement(icon, { width: 18, height: 18 }) + ) : ( + + )} + {title} + + + {description} + + + ) +} diff --git a/src/renderer/src/components/PrivacyPolicy/index.tsx b/src/renderer/src/components/PrivacyPolicy/index.tsx new file mode 100644 index 0000000..cde0c04 --- /dev/null +++ b/src/renderer/src/components/PrivacyPolicy/index.tsx @@ -0,0 +1,231 @@ +import React from 'react' +import { styled } from '@mui/material/styles' +import { useIntl } from 'react-intl' + +import { BLUE_GREY, DARK_GREY, VERY_LIGHT_GREY, WHITE } from '../../colors' +import BarChart from '../../images/BarChart.svg' +import BustInSilhouette from '../../images/BustInSilhouette.svg' +import Wrench from '../../images/Wrench.svg' +import ChevronUp from '../../images/chevrondown-expanded.svg' +import ChevronDown from '../../images/chevrondown.svg' +import ClosedLockWithKey from '../../images/closed_lock_with_key.png' +import RaisedFistMediumSkinTone from '../../images/raised_fist_medium_skin_tone.png' +import RaisedHandMediumSkinTone from '../../images/raised_hand_medium_skin_tone.png' +import RedDot from '../../images/red_dot.png' +import { Text } from '../Text' +import { DiagnosticItem } from './DiagnosticItem' +import { MetricsDiagnosticsPermissionToggle } from './MetricsDiagnosticsPermissionToggle' +import { PointContainer } from './PointContainer' +import { m } from './privacyPolicyMessages' + +const Container = styled('div')({ + display: 'flex', + flexDirection: 'column', + gap: 20, + padding: 20, + width: '100%', + maxWidth: 800, + margin: '0 auto', + overflowY: 'auto', +}) +const Header = styled(Text)({ + textAlign: 'center', +}) +const Subheader = styled(Text)({ + marginTop: 50, + fontSize: 24, + fontWeight: 'bold', + marginBottom: 30, +}) +const ContentBox = styled('div')({ + width: '70%', + maxWidth: 800, + margin: '0 auto', + padding: 20, + backgroundColor: WHITE, + borderRadius: 10, +}) +const HorizontalLine = styled('div')({ + borderBottom: `1px solid ${BLUE_GREY}`, + margin: '20px 0', +}) +const OverviewBox = styled('div')({ + padding: 20, + borderWidth: 1, + borderColor: BLUE_GREY, + borderRadius: 10, + backgroundColor: VERY_LIGHT_GREY, + marginBottom: 20, +}) +const ToggleContainer = styled('div')( + ({ isTop, isBottom }: { isTop?: boolean; isBottom?: boolean }) => ({ + border: `1px solid ${BLUE_GREY}`, + borderRadius: isTop ? '10px 10px 0 0' : isBottom ? '0 0 10px 10px' : '0', + borderTopWidth: isBottom ? 0 : 1, + padding: '20px 0', + margin: 0, + gap: 'unset', + }), +) +const ToggleHeader = styled('div')({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '16px 20px', + cursor: 'pointer', + backgroundColor: WHITE, +}) +const ToggleContent = styled('div')({ + padding: '16px 20px', + backgroundColor: WHITE, +}) +const ToggledText = styled(Text)(({ theme }) => ({ + color: DARK_GREY, + fontSize: theme.typography.body2.fontSize, +})) +const DiagnosticsContainer = styled('div')({ + border: `1px solid ${BLUE_GREY}`, + borderRadius: 10, + marginBottom: 20, + paddingBottom: 8, +}) +const DiagnosticsContent = styled('div')({ + paddingLeft: 20, +}) +const HorizontalLineSmall = styled('div')({ + borderBottom: `1px solid ${BLUE_GREY}`, + margin: '20px 20px 20px 0', +}) +const PermissionToggleContainer = styled('div')({ + flexDirection: 'row', + alignItems: 'center', + backgroundColor: WHITE, + padding: 15, + border: `1px solid ${BLUE_GREY}`, + borderRadius: 10, +}) + +export function PrivacyPolicy() { + const { formatMessage } = useIntl() + const [awanaExpanded, setAwanaExpanded] = React.useState(false) + const [openSourceExpanded, setOpenSourceExpanded] = React.useState(false) + + return ( + +
{formatMessage(m.privacyPolicyTitle)}
+ + + {formatMessage(m.overview)} + + + setAwanaExpanded((prev) => !prev)}> + {formatMessage(m.aboutAwana)} + {awanaExpanded ? ( + + ) : ( + + )} + + {awanaExpanded && ( + + {formatMessage(m.aboutAwanaContent)} + + )} + + + setOpenSourceExpanded((prev) => !prev)}> + {formatMessage(m.openSource)} + {openSourceExpanded ? ( + + ) : ( + + )} + + {openSourceExpanded && ( + + {formatMessage(m.openSourceContent)} + + )} + + {formatMessage(m.comapeoPrivacyPolicy)} + + + + + {formatMessage(m.dataCollection)} + + + + {formatMessage(m.diagnosticsTitle)} + + + + + + + + + + {formatMessage(m.appUsageTitle)} + + + + + + + + + {formatMessage(m.permissionsTitle)} + + + + +
+ ) +} diff --git a/src/renderer/src/components/PrivacyPolicy/privacyPolicyMessages.ts b/src/renderer/src/components/PrivacyPolicy/privacyPolicyMessages.ts new file mode 100644 index 0000000..7b2251b --- /dev/null +++ b/src/renderer/src/components/PrivacyPolicy/privacyPolicyMessages.ts @@ -0,0 +1,167 @@ +import { defineMessages } from 'react-intl' + +export const m = defineMessages({ + overview: { + id: 'screens.PrivacyPolicy.overview', + defaultMessage: + 'The following explains what information (or “data”) is sent from CoMapeo to the application developer, Awana Digital, and how that information is used.', + }, + aboutAwana: { + id: 'screens.PrivacyPolicy.aboutAwana', + defaultMessage: 'About Awana Digital', + }, + aboutAwanaContent: { + id: 'screens.PrivacyPolicy.aboutAwanaContent', + defaultMessage: + '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.', + }, + privacyPolicyTitle: { + id: 'screens.PrivacyPolicy.privacyPolicyTitle', + defaultMessage: 'Privacy Policy', + }, + comapeoPrivacyPolicy: { + id: 'screens.PrivacyPolicy.comapeoPrivacyPolicy', + defaultMessage: 'CoMapeo Data Privacy', + }, + openSource: { + id: 'screens.PrivacyPolicy.openSource', + defaultMessage: 'Open Source and the "Official" Version', + }, + openSourceContent: { + id: 'screens.PrivacyPolicy.openSourceContent', + defaultMessage: + 'CoMapeo is an open-source application.\n\nThis 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.\n\nThis 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.\n\nUnofficial releases of CoMapeo obtained from other channels are outside our control and may share additional information with other organizations.', + }, + privateByDefault: { + id: 'screens.PrivacyPolicy.privateByDefault', + defaultMessage: 'Private by default', + }, + privateByDefaultDescription: { + id: 'screens.PrivacyPolicy.privateByDefaultDescription', + defaultMessage: + "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.\n\nWhen 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.\n\nAwana Digital never sees nor has access to any of the data you collect with CoMapeo unless you explicitly send it to us.", + }, + noPII: { + id: 'screens.PrivacyPolicy.noPII', + defaultMessage: 'No personally identifiable information', + }, + noPIIDescription: { + id: 'screens.PrivacyPolicy.noPIIDescription', + defaultMessage: + 'Using CoMapeo does not require a user account.\n\nAwana Digital does not collect your name, email address or any other personal details.\n\nNo 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.', + }, + control: { + id: 'screens.PrivacyPolicy.control', + defaultMessage: "You're in control", + }, + controlDescription: { + id: 'screens.PrivacyPolicy.controlDescription', + defaultMessage: + 'You can opt out of sending any information to Awana Digital.\n\nYou 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.\n\nWe 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.', + }, + dataCollection: { + id: 'screens.PrivacyPolicy.dataCollection', + defaultMessage: 'CoMapeo Data Collection', + }, + whatIsCollected: { + id: 'screens.PrivacyPolicy.whatIsCollected', + defaultMessage: 'What is collected?', + }, + whatIsCollectedDescription: { + id: 'screens.PrivacyPolicy.whatIsCollectedDescription', + defaultMessage: + 'By default, anonymized diagnostic information about your device, app crashes, errors and performance is shared with Awana Digital.\n\nYou 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).', + }, + diagnosticsTitle: { + id: 'screens.PrivacyPolicy.diagnosticsTitle', + defaultMessage: 'Diagnostics', + }, + crashData: { + id: 'screens.PrivacyPolicy.crashData', + defaultMessage: 'Crash Data', + }, + crashDataDescription: { + id: 'screens.PrivacyPolicy.crashDataDescription', + defaultMessage: + 'Information about what caused the app to close unexpectedly', + }, + appErrors: { + id: 'screens.PrivacyPolicy.appErrors', + defaultMessage: 'App Errors', + }, + appErrorsDescription: { + id: 'screens.PrivacyPolicy.appErrorsDescription', + defaultMessage: + 'Information about internal errors that result in the app not functioning as expected', + }, + performanceData: { + id: 'screens.PrivacyPolicy.performanceData', + defaultMessage: 'Performance Data', + }, + performanceDataDescription: { + id: 'screens.PrivacyPolicy.performanceDataDescription', + defaultMessage: + 'Such as launch time, energy usage, app freezes, and responsiveness', + }, + deviceInfo: { + id: 'screens.PrivacyPolicy.deviceInfo', + defaultMessage: 'Device Info', + }, + deviceInfoDescription: { + id: 'screens.PrivacyPolicy.deviceInfoDescription', + defaultMessage: + 'Such as model and manufacturer of your device; device operating system; screen size; device locale (language); device memory.', + }, + appInfo: { + id: 'screens.PrivacyPolicy.appInfo', + defaultMessage: 'App Info', + }, + appInfoDescription: { + id: 'screens.PrivacyPolicy.appInfoDescription', + defaultMessage: 'The version and locale (language) of CoMapeo', + }, + appUsageTitle: { + id: 'screens.PrivacyPolicy.appUsageTitle', + defaultMessage: 'App Usage', + }, + userCount: { + id: 'screens.PrivacyPolicy.userCount', + defaultMessage: 'User Counts', + }, + userCountDescription: { + id: 'screens.PrivacyPolicy.userCountDescription', + defaultMessage: + 'The number of users per country and per project. Aggregated and anonymized', + }, + whyCollected: { + id: 'screens.PrivacyPolicy.whyCollected', + defaultMessage: 'Why is this data collected?', + }, + whyCollectedDescription: { + id: 'screens.PrivacyPolicy.whyCollectedDescription', + defaultMessage: + '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.', + }, + notCollected: { + id: 'screens.PrivacyPolicy.notCollected', + defaultMessage: 'What is not collected?', + }, + notCollectedDescription: { + id: 'screens.PrivacyPolicy.notCollectedDescription', + defaultMessage: + '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.\n\nDiagnostic 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.', + }, + thirdParty: { + id: 'screens.PrivacyPolicy.thirdParty', + defaultMessage: 'Third-party access to data', + }, + thirdPartyDescription: { + id: 'screens.PrivacyPolicy.thirdPartyDescription', + defaultMessage: + 'A "third-party" is an organization other than Awana Digital.', + }, + permissionsTitle: { + id: 'screens.OnboardingPrivacyPolicy.permissionsTitle', + defaultMessage: 'Current Permissions', + }, +}) diff --git a/src/renderer/src/images/BarChart.svg b/src/renderer/src/images/BarChart.svg new file mode 100644 index 0000000..6fe59a5 --- /dev/null +++ b/src/renderer/src/images/BarChart.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/renderer/src/images/BustInSilhouette.svg b/src/renderer/src/images/BustInSilhouette.svg new file mode 100644 index 0000000..c5f2857 --- /dev/null +++ b/src/renderer/src/images/BustInSilhouette.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/renderer/src/images/LockedWithKey.svg b/src/renderer/src/images/LockedWithKey.svg new file mode 100644 index 0000000..2daad0e --- /dev/null +++ b/src/renderer/src/images/LockedWithKey.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/src/images/Wrench.svg b/src/renderer/src/images/Wrench.svg new file mode 100644 index 0000000..8656b0d --- /dev/null +++ b/src/renderer/src/images/Wrench.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/renderer/src/images/chevrondown-expanded.svg b/src/renderer/src/images/chevrondown-expanded.svg new file mode 100644 index 0000000..86235ba --- /dev/null +++ b/src/renderer/src/images/chevrondown-expanded.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/images/chevrondown.svg b/src/renderer/src/images/chevrondown.svg new file mode 100644 index 0000000..384572e --- /dev/null +++ b/src/renderer/src/images/chevrondown.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/images/raised_hand_medium_skin_tone.png b/src/renderer/src/images/raised_hand_medium_skin_tone.png new file mode 100644 index 0000000..0c46823 Binary files /dev/null and b/src/renderer/src/images/raised_hand_medium_skin_tone.png differ diff --git a/src/renderer/src/images/red_dot.png b/src/renderer/src/images/red_dot.png new file mode 100644 index 0000000..68b4618 Binary files /dev/null and b/src/renderer/src/images/red_dot.png differ diff --git a/src/renderer/src/routeTree.gen.ts b/src/renderer/src/routeTree.gen.ts index bc13be6..3207835 100644 --- a/src/renderer/src/routeTree.gen.ts +++ b/src/renderer/src/routeTree.gen.ts @@ -10,16 +10,18 @@ import { createFileRoute } from '@tanstack/react-router' -import { Route as MapTabsMapImport } from './routes/(MapTabs)/_Map' -import { Route as MapTabsMapTab1Import } from './routes/(MapTabs)/_Map.tab1' -import { Route as MapTabsMapTab2Import } from './routes/(MapTabs)/_Map.tab2' -import { Route as OnboardingImport } from './routes/Onboarding' -import { Route as OnboardingDataPrivacyImport } from './routes/Onboarding.DataPrivacy' -import { Route as WelcomeImport } from './routes/Welcome' // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as WelcomeImport } from './routes/Welcome' import { Route as IndexImport } from './routes/index' +import { Route as OnboardingIndexImport } from './routes/Onboarding/index' +import { Route as OnboardingPrivacyPolicyScreenImport } from './routes/Onboarding/PrivacyPolicyScreen' +import { Route as OnboardingNextStepImport } from './routes/Onboarding/NextStep' +import { Route as OnboardingDataPrivacyImport } from './routes/Onboarding/DataPrivacy' +import { Route as MapTabsMapImport } from './routes/(MapTabs)/_Map' +import { Route as MapTabsMapTab2Import } from './routes/(MapTabs)/_Map.tab2' +import { Route as MapTabsMapTab1Import } from './routes/(MapTabs)/_Map.tab1' // Create Virtual Routes @@ -28,230 +30,263 @@ const MapTabsImport = createFileRoute('/(MapTabs)')() // Create/Update Routes const MapTabsRoute = MapTabsImport.update({ - id: '/(MapTabs)', - getParentRoute: () => rootRoute, + id: '/(MapTabs)', + getParentRoute: () => rootRoute, } as any) const WelcomeRoute = WelcomeImport.update({ - id: '/Welcome', - path: '/Welcome', - getParentRoute: () => rootRoute, + id: '/Welcome', + path: '/Welcome', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, } as any) -const OnboardingRoute = OnboardingImport.update({ - id: '/Onboarding', - path: '/Onboarding', - getParentRoute: () => rootRoute, +const OnboardingIndexRoute = OnboardingIndexImport.update({ + id: '/Onboarding/', + path: '/Onboarding/', + getParentRoute: () => rootRoute, } as any) -const IndexRoute = IndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRoute, +const OnboardingPrivacyPolicyScreenRoute = + OnboardingPrivacyPolicyScreenImport.update({ + id: '/Onboarding/PrivacyPolicyScreen', + path: '/Onboarding/PrivacyPolicyScreen', + getParentRoute: () => rootRoute, + } as any) + +const OnboardingNextStepRoute = OnboardingNextStepImport.update({ + id: '/Onboarding/NextStep', + path: '/Onboarding/NextStep', + getParentRoute: () => rootRoute, } as any) const OnboardingDataPrivacyRoute = OnboardingDataPrivacyImport.update({ - id: '/DataPrivacy', - path: '/DataPrivacy', - getParentRoute: () => OnboardingRoute, + id: '/Onboarding/DataPrivacy', + path: '/Onboarding/DataPrivacy', + getParentRoute: () => rootRoute, } as any) const MapTabsMapRoute = MapTabsMapImport.update({ - id: '/_Map', - getParentRoute: () => MapTabsRoute, + id: '/_Map', + getParentRoute: () => MapTabsRoute, } as any) const MapTabsMapTab2Route = MapTabsMapTab2Import.update({ - id: '/tab2', - path: '/tab2', - getParentRoute: () => MapTabsMapRoute, + id: '/tab2', + path: '/tab2', + getParentRoute: () => MapTabsMapRoute, } as any) const MapTabsMapTab1Route = MapTabsMapTab1Import.update({ - id: '/tab1', - path: '/tab1', - getParentRoute: () => MapTabsMapRoute, + id: '/tab1', + path: '/tab1', + getParentRoute: () => MapTabsMapRoute, } as any) // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexImport - parentRoute: typeof rootRoute - } - '/Onboarding': { - id: '/Onboarding' - path: '/Onboarding' - fullPath: '/Onboarding' - preLoaderRoute: typeof OnboardingImport - parentRoute: typeof rootRoute - } - '/Welcome': { - id: '/Welcome' - path: '/Welcome' - fullPath: '/Welcome' - preLoaderRoute: typeof WelcomeImport - parentRoute: typeof rootRoute - } - '/(MapTabs)': { - id: '/(MapTabs)' - path: '/' - fullPath: '/' - preLoaderRoute: typeof MapTabsImport - parentRoute: typeof rootRoute - } - '/(MapTabs)/_Map': { - id: '/(MapTabs)/_Map' - path: '/' - fullPath: '/' - preLoaderRoute: typeof MapTabsMapImport - parentRoute: typeof MapTabsRoute - } - '/Onboarding/DataPrivacy': { - id: '/Onboarding/DataPrivacy' - path: '/DataPrivacy' - fullPath: '/Onboarding/DataPrivacy' - preLoaderRoute: typeof OnboardingDataPrivacyImport - parentRoute: typeof OnboardingImport - } - '/(MapTabs)/_Map/tab1': { - id: '/(MapTabs)/_Map/tab1' - path: '/tab1' - fullPath: '/tab1' - preLoaderRoute: typeof MapTabsMapTab1Import - parentRoute: typeof MapTabsMapImport - } - '/(MapTabs)/_Map/tab2': { - id: '/(MapTabs)/_Map/tab2' - path: '/tab2' - fullPath: '/tab2' - preLoaderRoute: typeof MapTabsMapTab2Import - parentRoute: typeof MapTabsMapImport - } - } + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/Welcome': { + id: '/Welcome' + path: '/Welcome' + fullPath: '/Welcome' + preLoaderRoute: typeof WelcomeImport + parentRoute: typeof rootRoute + } + '/(MapTabs)': { + id: '/(MapTabs)' + path: '/' + fullPath: '/' + preLoaderRoute: typeof MapTabsImport + parentRoute: typeof rootRoute + } + '/(MapTabs)/_Map': { + id: '/(MapTabs)/_Map' + path: '/' + fullPath: '/' + preLoaderRoute: typeof MapTabsMapImport + parentRoute: typeof MapTabsRoute + } + '/Onboarding/DataPrivacy': { + id: '/Onboarding/DataPrivacy' + path: '/Onboarding/DataPrivacy' + fullPath: '/Onboarding/DataPrivacy' + preLoaderRoute: typeof OnboardingDataPrivacyImport + parentRoute: typeof rootRoute + } + '/Onboarding/NextStep': { + id: '/Onboarding/NextStep' + path: '/Onboarding/NextStep' + fullPath: '/Onboarding/NextStep' + preLoaderRoute: typeof OnboardingNextStepImport + parentRoute: typeof rootRoute + } + '/Onboarding/PrivacyPolicyScreen': { + id: '/Onboarding/PrivacyPolicyScreen' + path: '/Onboarding/PrivacyPolicyScreen' + fullPath: '/Onboarding/PrivacyPolicyScreen' + preLoaderRoute: typeof OnboardingPrivacyPolicyScreenImport + parentRoute: typeof rootRoute + } + '/Onboarding/': { + id: '/Onboarding/' + path: '/Onboarding' + fullPath: '/Onboarding' + preLoaderRoute: typeof OnboardingIndexImport + parentRoute: typeof rootRoute + } + '/(MapTabs)/_Map/tab1': { + id: '/(MapTabs)/_Map/tab1' + path: '/tab1' + fullPath: '/tab1' + preLoaderRoute: typeof MapTabsMapTab1Import + parentRoute: typeof MapTabsMapImport + } + '/(MapTabs)/_Map/tab2': { + id: '/(MapTabs)/_Map/tab2' + path: '/tab2' + fullPath: '/tab2' + preLoaderRoute: typeof MapTabsMapTab2Import + parentRoute: typeof MapTabsMapImport + } + } } // Create and export the route tree -interface OnboardingRouteChildren { - OnboardingDataPrivacyRoute: typeof OnboardingDataPrivacyRoute -} - -const OnboardingRouteChildren: OnboardingRouteChildren = { - OnboardingDataPrivacyRoute: OnboardingDataPrivacyRoute, -} - -const OnboardingRouteWithChildren = OnboardingRoute._addFileChildren( - OnboardingRouteChildren, -) - interface MapTabsMapRouteChildren { - MapTabsMapTab1Route: typeof MapTabsMapTab1Route - MapTabsMapTab2Route: typeof MapTabsMapTab2Route + MapTabsMapTab1Route: typeof MapTabsMapTab1Route + MapTabsMapTab2Route: typeof MapTabsMapTab2Route } const MapTabsMapRouteChildren: MapTabsMapRouteChildren = { - MapTabsMapTab1Route: MapTabsMapTab1Route, - MapTabsMapTab2Route: MapTabsMapTab2Route, + MapTabsMapTab1Route: MapTabsMapTab1Route, + MapTabsMapTab2Route: MapTabsMapTab2Route, } const MapTabsMapRouteWithChildren = MapTabsMapRoute._addFileChildren( - MapTabsMapRouteChildren, + MapTabsMapRouteChildren, ) interface MapTabsRouteChildren { - MapTabsMapRoute: typeof MapTabsMapRouteWithChildren + MapTabsMapRoute: typeof MapTabsMapRouteWithChildren } const MapTabsRouteChildren: MapTabsRouteChildren = { - MapTabsMapRoute: MapTabsMapRouteWithChildren, + MapTabsMapRoute: MapTabsMapRouteWithChildren, } const MapTabsRouteWithChildren = - MapTabsRoute._addFileChildren(MapTabsRouteChildren) + MapTabsRoute._addFileChildren(MapTabsRouteChildren) export interface FileRoutesByFullPath { - '/': typeof MapTabsMapRouteWithChildren - '/Onboarding': typeof OnboardingRouteWithChildren - '/Welcome': typeof WelcomeRoute - '/Onboarding/DataPrivacy': typeof OnboardingDataPrivacyRoute - '/tab1': typeof MapTabsMapTab1Route - '/tab2': typeof MapTabsMapTab2Route + '/': typeof MapTabsMapRouteWithChildren + '/Welcome': typeof WelcomeRoute + '/Onboarding/DataPrivacy': typeof OnboardingDataPrivacyRoute + '/Onboarding/NextStep': typeof OnboardingNextStepRoute + '/Onboarding/PrivacyPolicyScreen': typeof OnboardingPrivacyPolicyScreenRoute + '/Onboarding': typeof OnboardingIndexRoute + '/tab1': typeof MapTabsMapTab1Route + '/tab2': typeof MapTabsMapTab2Route } export interface FileRoutesByTo { - '/': typeof MapTabsMapRouteWithChildren - '/Onboarding': typeof OnboardingRouteWithChildren - '/Welcome': typeof WelcomeRoute - '/Onboarding/DataPrivacy': typeof OnboardingDataPrivacyRoute - '/tab1': typeof MapTabsMapTab1Route - '/tab2': typeof MapTabsMapTab2Route + '/': typeof MapTabsMapRouteWithChildren + '/Welcome': typeof WelcomeRoute + '/Onboarding/DataPrivacy': typeof OnboardingDataPrivacyRoute + '/Onboarding/NextStep': typeof OnboardingNextStepRoute + '/Onboarding/PrivacyPolicyScreen': typeof OnboardingPrivacyPolicyScreenRoute + '/Onboarding': typeof OnboardingIndexRoute + '/tab1': typeof MapTabsMapTab1Route + '/tab2': typeof MapTabsMapTab2Route } export interface FileRoutesById { - __root__: typeof rootRoute - '/': typeof IndexRoute - '/Onboarding': typeof OnboardingRouteWithChildren - '/Welcome': typeof WelcomeRoute - '/(MapTabs)': typeof MapTabsRouteWithChildren - '/(MapTabs)/_Map': typeof MapTabsMapRouteWithChildren - '/Onboarding/DataPrivacy': typeof OnboardingDataPrivacyRoute - '/(MapTabs)/_Map/tab1': typeof MapTabsMapTab1Route - '/(MapTabs)/_Map/tab2': typeof MapTabsMapTab2Route + __root__: typeof rootRoute + '/': typeof IndexRoute + '/Welcome': typeof WelcomeRoute + '/(MapTabs)': typeof MapTabsRouteWithChildren + '/(MapTabs)/_Map': typeof MapTabsMapRouteWithChildren + '/Onboarding/DataPrivacy': typeof OnboardingDataPrivacyRoute + '/Onboarding/NextStep': typeof OnboardingNextStepRoute + '/Onboarding/PrivacyPolicyScreen': typeof OnboardingPrivacyPolicyScreenRoute + '/Onboarding/': typeof OnboardingIndexRoute + '/(MapTabs)/_Map/tab1': typeof MapTabsMapTab1Route + '/(MapTabs)/_Map/tab2': typeof MapTabsMapTab2Route } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: - | '/' - | '/Onboarding' - | '/Welcome' - | '/Onboarding/DataPrivacy' - | '/tab1' - | '/tab2' - fileRoutesByTo: FileRoutesByTo - to: - | '/' - | '/Onboarding' - | '/Welcome' - | '/Onboarding/DataPrivacy' - | '/tab1' - | '/tab2' - id: - | '__root__' - | '/' - | '/Onboarding' - | '/Welcome' - | '/(MapTabs)' - | '/(MapTabs)/_Map' - | '/Onboarding/DataPrivacy' - | '/(MapTabs)/_Map/tab1' - | '/(MapTabs)/_Map/tab2' - fileRoutesById: FileRoutesById + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/Welcome' + | '/Onboarding/DataPrivacy' + | '/Onboarding/NextStep' + | '/Onboarding/PrivacyPolicyScreen' + | '/Onboarding' + | '/tab1' + | '/tab2' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/Welcome' + | '/Onboarding/DataPrivacy' + | '/Onboarding/NextStep' + | '/Onboarding/PrivacyPolicyScreen' + | '/Onboarding' + | '/tab1' + | '/tab2' + id: + | '__root__' + | '/' + | '/Welcome' + | '/(MapTabs)' + | '/(MapTabs)/_Map' + | '/Onboarding/DataPrivacy' + | '/Onboarding/NextStep' + | '/Onboarding/PrivacyPolicyScreen' + | '/Onboarding/' + | '/(MapTabs)/_Map/tab1' + | '/(MapTabs)/_Map/tab2' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - OnboardingRoute: typeof OnboardingRouteWithChildren - WelcomeRoute: typeof WelcomeRoute - MapTabsRoute: typeof MapTabsRouteWithChildren + IndexRoute: typeof IndexRoute + WelcomeRoute: typeof WelcomeRoute + MapTabsRoute: typeof MapTabsRouteWithChildren + OnboardingDataPrivacyRoute: typeof OnboardingDataPrivacyRoute + OnboardingNextStepRoute: typeof OnboardingNextStepRoute + OnboardingPrivacyPolicyScreenRoute: typeof OnboardingPrivacyPolicyScreenRoute + OnboardingIndexRoute: typeof OnboardingIndexRoute } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - OnboardingRoute: OnboardingRouteWithChildren, - WelcomeRoute: WelcomeRoute, - MapTabsRoute: MapTabsRouteWithChildren, + IndexRoute: IndexRoute, + WelcomeRoute: WelcomeRoute, + MapTabsRoute: MapTabsRouteWithChildren, + OnboardingDataPrivacyRoute: OnboardingDataPrivacyRoute, + OnboardingNextStepRoute: OnboardingNextStepRoute, + OnboardingPrivacyPolicyScreenRoute: OnboardingPrivacyPolicyScreenRoute, + OnboardingIndexRoute: OnboardingIndexRoute, } export const routeTree = rootRoute - ._addFileChildren(rootRouteChildren) - ._addFileTypes() + ._addFileChildren(rootRouteChildren) + ._addFileTypes() /* ROUTE_MANIFEST_START { @@ -260,20 +295,17 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", - "/Onboarding", "/Welcome", - "/(MapTabs)" + "/(MapTabs)", + "/Onboarding/DataPrivacy", + "/Onboarding/NextStep", + "/Onboarding/PrivacyPolicyScreen", + "/Onboarding/" ] }, "/": { "filePath": "index.tsx" }, - "/Onboarding": { - "filePath": "Onboarding.tsx", - "children": [ - "/Onboarding/DataPrivacy" - ] - }, "/Welcome": { "filePath": "Welcome.tsx" }, @@ -292,8 +324,16 @@ export const routeTree = rootRoute ] }, "/Onboarding/DataPrivacy": { - "filePath": "Onboarding.DataPrivacy.tsx", - "parent": "/Onboarding" + "filePath": "Onboarding/DataPrivacy.tsx" + }, + "/Onboarding/NextStep": { + "filePath": "Onboarding/NextStep.tsx" + }, + "/Onboarding/PrivacyPolicyScreen": { + "filePath": "Onboarding/PrivacyPolicyScreen.tsx" + }, + "/Onboarding/": { + "filePath": "Onboarding/index.tsx" }, "/(MapTabs)/_Map/tab1": { "filePath": "(MapTabs)/_Map.tab1.tsx", diff --git a/src/renderer/src/routes/Onboarding.DataPrivacy.tsx b/src/renderer/src/routes/Onboarding.DataPrivacy.tsx deleted file mode 100644 index 41dc365..0000000 --- a/src/renderer/src/routes/Onboarding.DataPrivacy.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react' -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/Onboarding/DataPrivacy')({ - component: DataPrivacyComponent, -}) - -function DataPrivacyComponent() { - return ( -
-

Step 1: User Information

-
- ) -} diff --git a/src/renderer/src/routes/Onboarding/DataPrivacy.tsx b/src/renderer/src/routes/Onboarding/DataPrivacy.tsx new file mode 100644 index 0000000..1139a37 --- /dev/null +++ b/src/renderer/src/routes/Onboarding/DataPrivacy.tsx @@ -0,0 +1,158 @@ +import { styled } from '@mui/material/styles' +import { createFileRoute, useNavigate } from '@tanstack/react-router' +import { defineMessages, useIntl } from 'react-intl' + +import { + BLACK, + BLUE_GREY, + DARK_COMAPEO_BLUE, + DARK_GREY, + WHITE, +} from '../../colors' +import { Button } from '../../components/Button' +import { OnboardingTopMenu } from '../../components/OnboardingTopMenu' +import { Text } from '../../components/Text' +import LockedIcon from '../../images/LockedWithKey.svg' + +export const m = defineMessages({ + title: { + id: 'screens.DataPrivacy.title', + defaultMessage: 'Review Data & Privacy', + }, + description: { + id: 'screens.DataPrivacy.description', + defaultMessage: + 'CoMapeo allows teams to map offline without needing internet servers.', + }, + dataPrivacyStays: { + id: 'screens.DataPrivacy.stays', + defaultMessage: 'Your data stays on your devices.', + }, + dataPrivacyEncrypted: { + id: 'screens.DataPrivacy.encrypted', + defaultMessage: 'All data stays fully encrypted', + }, + dataPrivacyManageAndControl: { + id: 'screens.DataPrivacy.manageAndControl', + defaultMessage: 'Easily manage and control sharing and collaboration.', + }, + dataPrivacyDiagnostic: { + id: 'screens.DataPrivacy.diagnostic', + defaultMessage: + 'Private by default — diagnostic information is made fully anonymous and you can opt-out any time.', + }, + learnMore: { + id: 'screens.DataPrivacy.learnMore', + defaultMessage: 'Learn More', + }, + next: { + id: 'screens.DataPrivacy.next', + defaultMessage: 'Next', + }, +}) + +export const Route = createFileRoute('/Onboarding/DataPrivacy')({ + component: DataPrivacyComponent, +}) + +const Container = styled('div')({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + backgroundColor: DARK_COMAPEO_BLUE, +}) +const ContentBox = styled('div')({ + backgroundColor: 'rgba(255, 255, 255, 0.94)', + border: `1px solid ${BLUE_GREY}`, + borderRadius: 8, + padding: 20, + width: '55%', + textAlign: 'center', + boxShadow: '0px 4px 4px 0px rgba(0, 0, 0, 0.02)', +}) + +const BodyTextWrapper = styled('div')({ + maxWidth: '40%', + margin: '16px auto 0', + textAlign: 'center', +}) +const ButtonContainer = styled('div')({ + display: 'flex', + justifyContent: 'space-between', + gap: 15, + marginTop: 63, + padding: '0 20px', +}) +const StyledIcon = styled(LockedIcon)({ + marginBottom: '16px', + width: 40, + height: 50, +}) +const BulletList = styled('ul')({ + width: '50%', + textAlign: 'left', + margin: '16px auto', + color: DARK_GREY, + paddingLeft: 0, +}) +const BulletListItem = styled('li')({ + marginBottom: 8, +}) +export function DataPrivacyComponent() { + const navigate = useNavigate() + const { formatMessage } = useIntl() + + return ( + + + + + {formatMessage(m.title)} + + + {formatMessage(m.description)} + + + + + {formatMessage(m.dataPrivacyStays)} + + + {formatMessage(m.dataPrivacyEncrypted)} + + + + {formatMessage(m.dataPrivacyManageAndControl)} + + + + {formatMessage(m.dataPrivacyDiagnostic)} + + + + + + + + + ) +} diff --git a/src/renderer/src/routes/Onboarding/NextStep.tsx b/src/renderer/src/routes/Onboarding/NextStep.tsx new file mode 100644 index 0000000..0280473 --- /dev/null +++ b/src/renderer/src/routes/Onboarding/NextStep.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/Onboarding/NextStep')({ + component: RouteComponent, +}) + +function RouteComponent() { + return 'Hello /Onboarding/NextStep!' +} diff --git a/src/renderer/src/routes/Onboarding/PrivacyPolicyScreen.tsx b/src/renderer/src/routes/Onboarding/PrivacyPolicyScreen.tsx new file mode 100644 index 0000000..f92f975 --- /dev/null +++ b/src/renderer/src/routes/Onboarding/PrivacyPolicyScreen.tsx @@ -0,0 +1,41 @@ +import { styled } from '@mui/material/styles' +import { createFileRoute } from '@tanstack/react-router' + +import { BLUE_GREY, DARK_COMAPEO_BLUE } from '../../colors' +import { OnboardingTopMenu } from '../../components/OnboardingTopMenu' +import { PrivacyPolicy } from '../../components/PrivacyPolicy' + +export const Route = createFileRoute('/Onboarding/PrivacyPolicyScreen')({ + component: PrivacyPolicyScreen, +}) + +const Container = styled('div')({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + backgroundColor: DARK_COMAPEO_BLUE, +}) +const ContentBox = styled('div')({ + width: '55%', + height: '80%', + backgroundColor: '#FFFFFF', + padding: '20px 30px', + borderRadius: 8, + border: `1px solid ${BLUE_GREY}`, + margin: '16px auto', + boxShadow: '0px 4px 4px 0px rgba(0, 0, 0, 0.02)', + overflowY: 'auto', +}) + +export function PrivacyPolicyScreen() { + return ( + + + + + + + ) +} diff --git a/src/renderer/src/routes/Onboarding.tsx b/src/renderer/src/routes/Onboarding/index.tsx similarity index 88% rename from src/renderer/src/routes/Onboarding.tsx rename to src/renderer/src/routes/Onboarding/index.tsx index ef1c48f..3968393 100644 --- a/src/renderer/src/routes/Onboarding.tsx +++ b/src/renderer/src/routes/Onboarding/index.tsx @@ -2,16 +2,16 @@ import { styled } from '@mui/material/styles' import { createFileRoute, useNavigate } from '@tanstack/react-router' import { defineMessages, useIntl } from 'react-intl' -import { CORNFLOWER_BLUE, DARK_COMAPEO_BLUE, ORANGE, WHITE } from '../colors' -import { Button } from '../components/Button' -import { Text } from '../components/Text' -import TopoBackground from '../images/TopoLogo.svg' -import Calling from '../images/calling.png' -import LockedWithKey from '../images/closed_lock_with_key.png' -import RaisedFistMediumSkinTone from '../images/raised_fist_medium_skin_tone.png' -import WorldMap from '../images/world_map.png' - -export const Route = createFileRoute('/Onboarding')({ +import { CORNFLOWER_BLUE, DARK_COMAPEO_BLUE, ORANGE, WHITE } from '../../colors' +import { Button } from '../../components/Button' +import { Text } from '../../components/Text' +import TopoBackground from '../../images/TopoLogo.svg' +import Calling from '../../images/calling.png' +import LockedWithKey from '../../images/closed_lock_with_key.png' +import RaisedFistMediumSkinTone from '../../images/raised_fist_medium_skin_tone.png' +import WorldMap from '../../images/world_map.png' + +export const Route = createFileRoute('/Onboarding/')({ component: OnboardingComponent, }) @@ -144,7 +144,7 @@ const StyledText = styled(Text)(({ theme }) => ({ fontSize: theme.typography.caption.fontSize, })) -function OnboardingComponent() { +export function OnboardingComponent() { const navigate = useNavigate() const { formatMessage } = useIntl()