Skip to content

Commit

Permalink
[4.1] update website and docs, add pagination to reminders (#484)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdanilowicz authored Aug 24, 2023
1 parent d9768b8 commit 54c460b
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 89 deletions.
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:[email protected]">Support</Link>
<Link href="mailto:[email protected]">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

0 comments on commit 54c460b

Please sign in to comment.