Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.1] update website and docs, add pagination to reminders #484

Merged
merged 5 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@
<img src="https://raw.githubusercontent.com/Left-on-Read/leftonread/main/app/assets/LogoWithText.svg" />
</h2>


[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Wow,%20super%20cool%20open-source%20project%20-%20check%20out%20Left%20on%20Read&url=https://leftonread.me&hashtags=texting,analytics,buildinpublic)

Your texting data never leaves your computer. We are open-source for this reason. Privacy comes first.

Left on Read is built with Electron, React, SQLite, Typescript.
Left on Read is built with Electron, React, SQLite, Typescript.

## Features:

- top sent word, emoji, contact
- your texting activity
- reactions sent in group chats
- filter by a word, friend, or time range
- sentiment analysis
- "Your Year in Text" experience (similiar to Spotify Wrapped)
- 🔐 code runs locally, no data leaves your computer
- 📊 top sent and received word, emoji, contact
- 🍆 your texting activity
- 😂 reactions sent in group chats
- 🔍 filter by a word, friend, or time range
- 💯 sentiment analysis
- 🎁 "Your Year in Text" experience a.k.a iMessage Wrapped

## Download Left on Read for Mac

Expand All @@ -26,11 +24,11 @@ Left on Read is built with Electron, React, SQLite, Typescript.

## Mission and Values

**Open-Source Transparency**: We open-sourced the entire application to keep users' security and privacy first.
**Open-Source**: We open-sourced the entire application to keep users' security and privacy first.

**Secure**: Just like your private photos and important documents, your text messages are only accessible to you and never seen by us.

**Fun**: The first iteration was a [web application](https://www.reddit.com/r/dataisbeautiful/comments/biou3e/4_years_of_texts_between_me_and_my_long_distance/), but now we have rebuilt Left on Read as a Desktop app.
**Fun**: The first iteration was a [web application](https://www.reddit.com/r/dataisbeautiful/comments/biou3e/4_years_of_texts_between_me_and_my_long_distance/), but now we have rebuilt Left on Read as a Desktop app, so that everything runs locally.

## License

Expand Down
2 changes: 1 addition & 1 deletion app/src/components/Dashboard/Wrapped/WrappedShareModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function WrappedShareModal({
<ModalCloseButton />
<ModalBody style={{ width: '60vw' }}>
<Text>
Share to Instagram, TikTok, and Twitter with{` `}
Share to your socials{` `}
<span
style={{
fontWeight: 600,
Expand Down
11 changes: 11 additions & 0 deletions app/src/components/Graphs/GraphContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ export function GraphContainer({
onClickMessageSchedulerRefresh,
setIsShareOpen,
showGroupChatShareButton,
nextButton,
backButton,
}: {
title: string[];
description?: string;
icon: IconType;
children: React.ReactNode;
tooltip?: React.ReactNode;
nextButton?: React.ReactNode;
backButton?: React.ReactNode;
isPremiumGraph?: boolean;
onClickMessageScheduler?: () => void;
onClickMessageSchedulerRefresh?: () => void;
Expand Down Expand Up @@ -164,6 +168,13 @@ export function GraphContainer({
</Button>
</div>
)}

{nextButton && backButton && (
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '20px' }}>{backButton}</div>
{nextButton}
</div>
)}
</div>
</div>
<div
Expand Down
141 changes: 83 additions & 58 deletions app/src/components/Graphs/RespondReminders.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
import {
Box,
Button,
Expand All @@ -17,6 +18,7 @@ import { typeMessageToPhoneNumber } from '../../utils/appleScriptCommands';
import { GraphContainer } from './GraphContainer';

export function RespondReminders() {
const [currentPage, setCurrentPage] = useState(0);
const NUMBER_OF_REMINDERS = 3;
const [isLoadingReminderArray, setIsLoadingReminderArray] = useState<
boolean[]
Expand Down Expand Up @@ -54,73 +56,96 @@ export function RespondReminders() {
))}
</>
) : (
reminders.slice(0, NUMBER_OF_REMINDERS).map((reminder, i) => {
return (
<Box
key={reminder.friend}
style={{
border: `1px solid ${defaultTheme.colors.gray['200']}`,
padding: 32,
borderRadius: 16,
}}
shadow="xl"
>
<Text color="gray.500" fontSize={14}>
From{' '}
<span
style={{
fontWeight: 'bold',
color: defaultTheme.colors.blue['400'],
}}
>
{reminder.friend}
</span>
<span style={{ margin: '0 6px' }}>on</span>
<span style={{ fontWeight: 'bold' }}>
{new Date(reminder.date).toLocaleString()}
</span>
</Text>
reminders
.slice(
currentPage * NUMBER_OF_REMINDERS,
(currentPage + 1) * NUMBER_OF_REMINDERS
)
.map((reminder, i) => {
return (
<Box
key={reminder.friend}
style={{
border: `1px solid ${defaultTheme.colors.gray['200']}`,
padding: 32,
borderRadius: 16,
}}
shadow="xl"
>
<Text color="gray.500" fontSize={14}>
From{' '}
<span
style={{
fontWeight: 'bold',
color: defaultTheme.colors.blue['400'],
}}
>
{reminder.friend}
</span>
<span style={{ margin: '0 6px' }}>on</span>
<span style={{ fontWeight: 'bold' }}>
{new Date(reminder.date).toLocaleString()}
</span>
</Text>

<Text style={{ marginTop: 8 }}>{reminder.message}</Text>
<Box style={{ marginTop: 24 }}>
<Button
isLoading={isLoadingReminderArray[i]}
loadingText="Opening iMessage..."
tabIndex={-1}
colorScheme="blue"
size="sm"
onClick={async () => {
const temp = [...isLoadingReminderArray];
temp[i] = true;
setIsLoadingReminderArray(temp);
await typeMessageToPhoneNumber({
message: 'Hey, meant to follow up on this earlier!',
// NOTE(Danilowicz): if we get reports of this not working,
// we should use the phone number here, which might have a
// a higher success rate
phoneNumber: reminder.friend,
});
const temp2 = [...isLoadingReminderArray];
temp2[i] = false;
setIsLoadingReminderArray(temp2);
logEvent({
eventName: 'RESPOND_TO_REMINDER',
});
}}
>
Respond
</Button>
<Text style={{ marginTop: 8 }}>{reminder.message}</Text>
<Box style={{ marginTop: 24 }}>
<Button
isLoading={isLoadingReminderArray[i]}
loadingText="Opening iMessage..."
tabIndex={-1}
colorScheme="blue"
size="sm"
onClick={async () => {
const temp = [...isLoadingReminderArray];
temp[i] = true;
setIsLoadingReminderArray(temp);
await typeMessageToPhoneNumber({
message: 'Hey, meant to follow up on this earlier!',
// NOTE(Danilowicz): if we get reports of this not working,
// we should use the phone number here, which might have a
// a higher success rate
phoneNumber: reminder.friend,
});
const temp2 = [...isLoadingReminderArray];
temp2[i] = false;
setIsLoadingReminderArray(temp2);
logEvent({
eventName: 'RESPOND_TO_REMINDER',
});
}}
>
Respond
</Button>
</Box>
</Box>
</Box>
);
})
);
})
);

return (
<GraphContainer
title={['Reminders']}
description="Did you forget to respond to these messages?"
icon={FiVoicemail}
backButton={
<Button
leftIcon={<ChevronLeftIcon />}
disabled={currentPage === 0}
onClick={() => setCurrentPage(currentPage - 1)}
>
Previous
</Button>
}
nextButton={
<Button
rightIcon={<ChevronRightIcon />}
disabled={reminders.length <= (currentPage + 1) * NUMBER_OF_REMINDERS}
onClick={() => setCurrentPage(currentPage + 1)}
>
Next
</Button>
}
>
<Stack spacing={8}>
{error && <Text color="red.400">Uh oh! Something went wrong... </Text>}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/ipcListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function attachIpcListeners() {
}
);

ipcMain.handle('query-respond-reminders', async (event) => {
ipcMain.handle('query-respond-reminders', async () => {
const db = getDb();
return queryRespondReminders(db);
});
Expand Down
20 changes: 11 additions & 9 deletions web/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Box, Icon, Stack, Text } from '@chakra-ui/react'
import { Box, Stack, Text } from '@chakra-ui/react'
import Image from 'next/image'
import Link from 'next/link'
import React from 'react'
import { FiLayers, FiMap, FiUser } from 'react-icons/fi'

import { DefaultContentContainer } from './DefaultContentContainer'

Expand Down Expand Up @@ -38,10 +37,10 @@ export function Footer() {
<Image src={'/LogoWithText.svg'} layout="fill" />
</Box>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<Icon as={FiMap} style={{ marginRight: 8 }} />
{/* <Icon as={FiMap} style={{ marginRight: 8 }} /> */}
San Francisco, US
</Box>
<Box>© Left on Read 2022</Box>
<Box>© Left on Read 2023</Box>
</Stack>
<Stack style={{ display: 'flex', justifyContent: 'space-between' }}>
<Stack fontSize="md">
Expand All @@ -54,7 +53,7 @@ export function Footer() {
md: '32px',
}}
>
<Icon as={FiLayers} style={{ marginRight: 8 }} />
{/* <Icon as={FiLayers} style={{ marginRight: 8 }} /> */}
Product
</Text>
<Link href="/">Download</Link>
Expand Down Expand Up @@ -82,13 +81,16 @@ export function Footer() {
md: '32px',
}}
>
<Icon as={FiUser} style={{ marginRight: 8 }} />
Contact
{/* <Icon as={FiUser} style={{ marginRight: 8 }} /> */}
Contact & Support
</Text>
<Link href="mailto:help.leftonread@gmail.com">Support</Link>
<Link href="mailto:help.leftonread@gmail.com">Email Us</Link>
<Link href="https://github.com/Left-on-Read/leftonread">
Github
Open-source on Github
</Link>
<Link href="https://billing.stripe.com/p/login/eVabK06mUcNG2oE6oo">
Manage Subscription
</Link>{' '}
</Stack>
<Box>
<a
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/sections/Download.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function Download({
lg: 'start',
}}
>
Relive your best texts of 2022. Free to try and built for you.
Relive your best texts of the year. Free to try and built for you.
</Text>
<Button
colorScheme="purple"
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/sections/Security.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ export function Security() {
<Box width={{ base: '100%', lg: '50%' }}>
<Box
fontSize={{
base: '2xl',
md: '3xl',
lg: '4xl',
base: '3xl',
md: '4xl',
lg: '5xl',
}}
fontWeight="extrabold"
style={{
Expand Down
8 changes: 4 additions & 4 deletions web/src/components/sections/Wrapped.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function Wrapped() {
style={{ margin: '12px 0' }}
>
Download today to revisit your funniest messages, group chats, and
words of 2022.
words of the year.
</Text>
</div>
<Marquee
Expand All @@ -110,7 +110,7 @@ export function Wrapped() {
color="blue.500"
fontWeight="bold"
>
data dawgs
the chatgpt fam
</Text>
</MarqueeItem>

Expand Down Expand Up @@ -210,7 +210,7 @@ export function Wrapped() {
color="orange.500"
fontWeight="bold"
>
stonks
AI
</Text>
</MarqueeItem>
<MarqueeItem>
Expand All @@ -231,7 +231,7 @@ export function Wrapped() {
color="orange.500"
fontWeight="bold"
>
🤡
🍆
</Text>
</MarqueeItem>
</Marquee>
Expand Down
4 changes: 4 additions & 0 deletions web/src/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export const MIN_HEIGHT = '720px'

// SEE THEME COLOURS HERE: https://chakra-ui.com/docs/styled-system/theme
export const chakraTheme = extendTheme({
config: {
initialColorMode: 'light',
useSystemColorMode: false,
},
colors: {
primary: baseTheme.colors.purple,
},
Expand Down
Loading