Skip to content

Commit 9c02d49

Browse files
committed
Remove sync prompt in favour of onboarding flow
Signed-off-by: Marcus Crane <[email protected]>
1 parent 78034b6 commit 9c02d49

File tree

6 files changed

+267
-79
lines changed

6 files changed

+267
-79
lines changed

backend/init.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ func (b *Backend) GetPlainSystemDetails() string {
7070
}
7171

7272
func (b *Backend) FormatSystemDetails() string {
73-
return fmt.Sprintf("<details><summary>System Details</summary><ul><li>Version: %s</li><li>Platform: %s</li><li>Architecture: %s</li></details>", b.version, runtime.GOOS, runtime.GOARCH)
73+
onboardingComplete := false
74+
if b.Settings.ReadwiseToken != "" {
75+
onboardingComplete = true
76+
}
77+
return fmt.Sprintf("<details><summary>System Details</summary><ul><li>Version: %s</li><li>Platform: %s</li><li>Architecture: %s</li><li>Onboarding Complete: %t</li></details>", b.version, runtime.GOOS, runtime.GOARCH, onboardingComplete)
7478
}
7579

7680
func (b *Backend) NavigateExplorerToLogLocation() {

backend/settings.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ func LoadSettings(portable bool) (*Settings, error) {
2323
return nil, errors.Wrap(err, "Failed to create settings directory. Do you have proper permissions?")
2424
}
2525
s := &Settings{
26-
path: settingsPath,
27-
UploadCovers: false,
26+
path: settingsPath,
27+
UploadStoreHighlights: true, // default on as users with only store purchased books are blocked from usage otherwise but give ample warning during setup
28+
UploadCovers: false,
2829
}
2930
b, err := os.ReadFile(settingsPath)
3031
if err != nil {

frontend/src/components/Navbar.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function Navbar() {
77
return (
88
<header className="flex p-3">
99
<div className="w-full text-left text-gray-900 dark:text-gray-300">
10-
{location.pathname === "/overview" && <NavLink to="/"><CpuChipIcon className="h-5 w-5 inline-block" /> Pick a different device</NavLink>}
10+
{location.pathname === "/overview" && <NavLink to="/selector"><CpuChipIcon className="h-5 w-5 inline-block" /> Pick a different device</NavLink>}
1111
{location.pathname === "/settings" && <NavLink to="/overview"><BookmarkIcon className="h-5 w-5 inline-block" /> Return to device overview</NavLink>}
1212
</div>
1313
<div className="w-full text-right text-gray-900 dark:text-gray-300">

frontend/src/main.jsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import { Toaster } from 'react-hot-toast'
66
import DeviceSelector from './pages/DeviceSelector';
77
import Overview from './pages/Overview';
88
import Settings from './pages/Settings';
9+
import Onboarding from './pages/Onboarding'
910

1011
import './style.css';
1112

1213
const routes = (
1314
<React.StrictMode>
1415
<HashRouter>
1516
<Routes>
16-
<Route path="/" element={<DeviceSelector />} />
17+
<Route path="/" element={<Onboarding />} />
18+
<Route path="/selector" element={<DeviceSelector />} />
1719
<Route path="/overview" element={<Overview />} />
1820
<Route path="/settings" element={<Settings />} />
1921
</Routes>

frontend/src/pages/Onboarding.jsx

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import React, { useState, useEffect } from 'react'
2+
import { useNavigate } from 'react-router-dom'
3+
import Navbar from "../components/Navbar"
4+
import logo from '../logo.png'
5+
import {BrowserOpenURL} from "../../wailsjs/runtime";
6+
import {
7+
FormatSystemDetails,
8+
GetPlainSystemDetails,
9+
GetSettings,
10+
NavigateExplorerToLogLocation
11+
} from "../../wailsjs/go/backend/Backend";
12+
import {SaveCoverUploading, SaveStoreHighlights, SaveToken} from "../../wailsjs/go/backend/Settings";
13+
import {toast} from "react-hot-toast";
14+
import {CheckTokenValidity} from "../../wailsjs/go/backend/Readwise";
15+
16+
export default function Onboarding() {
17+
const [loaded, setLoadState] = useState(false);
18+
const [onboardingComplete, setOnboardingComplete] = useState(false)
19+
const [token, setToken] = useState("")
20+
const [coversUploading, setCoversUploading] = useState(false);
21+
const [storeHighlights, setStoreHighlights] = useState(false); // default on as users run into this issue more than not but give ample warning
22+
const [tokenInput, setTokenInput] = useState("");
23+
const [systemDetails, setSystemDetails] = useState(
24+
"Fetching system details..."
25+
);
26+
27+
useEffect(() => {
28+
GetSettings().then((settings) => {
29+
setLoadState(true);
30+
if (settings.readwise_token !== "") {
31+
setOnboardingComplete(true)
32+
}
33+
setToken(settings.readwise_token);
34+
setTokenInput(settings.readwise_token);
35+
setCoversUploading(settings.upload_covers);
36+
setStoreHighlights(settings.upload_store_highlights)
37+
});
38+
GetPlainSystemDetails().then((details) => setSystemDetails(details));
39+
}, [loaded]);
40+
41+
function saveAllSettings() {
42+
if (tokenInput === "") {
43+
toast.error("Please enter your Readwise token")
44+
return
45+
}
46+
SaveToken(tokenInput);
47+
SaveCoverUploading(coversUploading);
48+
SaveStoreHighlights(storeHighlights);
49+
navigate("/selector")
50+
}
51+
52+
function checkTokenValid() {
53+
toast.promise(CheckTokenValidity(tokenInput), {
54+
loading: "Contacting Readwise...",
55+
success: () => "Your API token is valid!",
56+
error: (err) => {
57+
if (err === "401 Unauthorized") {
58+
return "Readwise rejected your token";
59+
}
60+
return err;
61+
},
62+
});
63+
}
64+
65+
const navigate = useNavigate()
66+
67+
if (onboardingComplete) {
68+
navigate("/selector")
69+
}
70+
return (
71+
<div className="min-h-screen bg-gray-100 dark:bg-gray-800 flex flex-col">
72+
<Navbar />
73+
<div className="flex-grow items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14">
74+
<div className="space-y-2">
75+
<img
76+
className="mx-auto h-36 w-auto logo-animation"
77+
src={logo}
78+
alt="The October logo, which is a cartoon octopus reading a book."
79+
/>
80+
<h2 className="text-center text-3xl font-extrabold text-gray-900 dark:dark:text-gray-300">
81+
First time setup with October
82+
</h2>
83+
<p className="mt-0 text-center text-sm text-gray-600 dark:text-gray-400">
84+
This should only take a minute of your time
85+
</p>
86+
</div>
87+
<div className="space-y-4">
88+
<div className="bg-white dark:bg-slate-700 shadow sm:rounded-lg">
89+
<div className="px-4 py-5 sm:p-6">
90+
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-300">
91+
Set your Readwise access token
92+
</h3>
93+
<div className="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
94+
<p>
95+
You can find your access token at{" "}
96+
<button
97+
className="text-gray-600 dark:text-gray-400 underline"
98+
onClick={() =>
99+
BrowserOpenURL("https://readwise.io/access_token")
100+
}
101+
>
102+
https://readwise.io/access_token
103+
</button>
104+
</p>
105+
</div>
106+
<form
107+
onSubmit={(e) => e.preventDefault()}
108+
className="sm:flex flex-col"
109+
>
110+
<div className="w-full mt-4 sm:flex sm:items-center">
111+
<input
112+
onChange={(e) => setTokenInput(e.target.value)}
113+
type="text"
114+
name="token"
115+
id="token"
116+
className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:bg-gray-200 focus:bg-white rounded-md"
117+
placeholder="Your access token goes here"
118+
value={tokenInput}
119+
/>
120+
</div>
121+
<div className="w-full mt-4 sm:flex flex-row">
122+
<button
123+
onClick={checkTokenValid}
124+
type="submit"
125+
className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:text-sm"
126+
>
127+
Validate
128+
</button>
129+
</div>
130+
</form>
131+
</div>
132+
</div>
133+
<div className="bg-white dark:bg-slate-700 shadow sm:rounded-lg">
134+
<div className="shadow overflow-hidden sm:rounded-md">
135+
<div className="px-4 py-5 bg-white dark:bg-slate-700 space-y-6 sm:p-6">
136+
<fieldset>
137+
<legend className="text-base font-medium text-gray-900 dark:text-gray-300">
138+
Highlight Settings
139+
</legend>
140+
<div className="mt-4 space-y-4">
141+
<div className="flex items-start">
142+
<div className="flex items-center h-5">
143+
<input
144+
// TODO: This probably causes the render method to seize up
145+
onInput={(e) => {
146+
setStoreHighlights(!e.currentTarget.checked)
147+
}}
148+
checked={storeHighlights}
149+
id="storeBought"
150+
name="storeBought"
151+
type="checkbox"
152+
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
153+
/>
154+
</div>
155+
<div className="ml-3 text-sm">
156+
<label
157+
htmlFor="storeBought"
158+
className="font-medium text-gray-700 dark:text-gray-300"
159+
>
160+
Upload highlights from store-bought books
161+
</label>
162+
<p className="text-red-500 dark:text-red-400">
163+
WARNING: If you are using the{" "}
164+
<button
165+
className="text-gray-600 dark:text-gray-400 underline"
166+
onClick={() =>
167+
BrowserOpenURL(
168+
"https://help.readwise.io/article/135-how-do-i-import-highlights-from-kobo"
169+
)
170+
}
171+
>
172+
official Readwise integration
173+
</button>{" "}
174+
to sync books from the Kobo store, you should <strong>disable</strong> this option or risk having duplicate highlights!
175+
</p>
176+
</div>
177+
</div>
178+
</div>
179+
<div className="mt-4 space-y-4">
180+
<div className="flex items-start">
181+
<div className="flex items-center h-5">
182+
<input
183+
// TODO: This probably causes the render method to seize up
184+
onInput={(e) =>
185+
setCoversUploading(!e.currentTarget.checked)
186+
}
187+
checked={coversUploading}
188+
id="comments"
189+
name="comments"
190+
type="checkbox"
191+
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
192+
/>
193+
</div>
194+
<div className="ml-3 text-sm">
195+
<label
196+
htmlFor="comments"
197+
className="font-medium text-gray-700 dark:text-gray-300"
198+
>
199+
Upload covers
200+
</label>
201+
<p className="text-gray-500 dark:text-gray-400">
202+
This will slow down the upload process a bit. It also
203+
requires you to have <button className="text-gray-600 dark:text-gray-400 underline" onClick={() => BrowserOpenURL("https://october.utf9k.net/prerequisites#configuring-calibre-to-sync-high-quality-covers")}>configured Calibre</button> to get the most benefit.
204+
</p>
205+
</div>
206+
</div>
207+
</div>
208+
</fieldset>
209+
</div>
210+
</div>
211+
</div>
212+
<div className="bg-white dark:bg-slate-700 shadow sm:rounded-lg">
213+
<div className="shadow overflow-hidden sm:rounded-md">
214+
<div className="px-4 py-5 bg-white dark:bg-slate-700 space-y-6 sm:p-6">
215+
<fieldset>
216+
<legend className="text-base font-medium text-gray-900 dark:text-gray-300">
217+
All done?
218+
</legend>
219+
<div className="space-y-4">
220+
<div className="flex items-start">
221+
<div className="w-full mt-4 sm:flex flex-row">
222+
<button
223+
onClick={() =>
224+
FormatSystemDetails().then((details) =>
225+
BrowserOpenURL(
226+
`https://github.com/marcus-crane/october/issues/new?body=${encodeURI(
227+
"I have an issue with...\n\n---\n\n" + details
228+
)}`
229+
)
230+
)
231+
}
232+
type="submit"
233+
className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:mt-0 sm:text-sm"
234+
>
235+
Having trouble?
236+
</button>
237+
<button
238+
onClick={saveAllSettings}
239+
type="submit"
240+
className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:mt-0 sm:ml-3 sm:text-sm"
241+
>
242+
Complete setup
243+
</button>
244+
</div>
245+
</div>
246+
</div>
247+
</fieldset>
248+
</div>
249+
</div>
250+
</div>
251+
</div>
252+
</div>
253+
</div>
254+
)
255+
}

frontend/src/pages/Overview.jsx

-74
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import Navbar from "../components/Navbar";
33
import { toast } from "react-hot-toast"
44
import { CountDeviceBookmarks } from "../../wailsjs/go/backend/Kobo";
55
import { GetSettings, GetSelectedKobo, ForwardToReadwise } from "../../wailsjs/go/backend/Backend";
6-
import { MarkUploadStorePromptShown } from "../../wailsjs/go/backend/Settings";
7-
import { Dialog, Transition } from '@headlessui/react'
8-
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
96

107
export default function Overview(props) {
118
const [settingsLoaded, setSettingsLoaded] = useState(false)
@@ -121,77 +118,6 @@ export default function Overview(props) {
121118
</div>
122119
</div>
123120
</div>
124-
<Transition.Root show={storeUploadWarningOpen} as={Fragment}>
125-
<Dialog as="div" className="relative z-10" initialFocus={cancelButtonRef} onClose={setStoreUploadWarningOpen}>
126-
<Transition.Child
127-
as={Fragment}
128-
enter="ease-out duration-300"
129-
enterFrom="opacity-0"
130-
enterTo="opacity-100"
131-
leave="ease-in duration-200"
132-
leaveFrom="opacity-100"
133-
leaveTo="opacity-0"
134-
>
135-
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
136-
</Transition.Child>
137-
138-
<div className="fixed inset-0 z-10 overflow-y-auto">
139-
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
140-
<Transition.Child
141-
as={Fragment}
142-
enter="ease-out duration-300"
143-
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
144-
enterTo="opacity-100 translate-y-0 sm:scale-100"
145-
leave="ease-in duration-200"
146-
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
147-
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
148-
>
149-
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white dark:bg-slate-700 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
150-
<div className="sm:flex sm:items-start">
151-
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
152-
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
153-
</div>
154-
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
155-
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900 dark:text-gray-300">
156-
Want to sync highlights from store-purchased books?
157-
</Dialog.Title>
158-
<div className="mt-2">
159-
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
160-
You appear to have some highlights from officially purchased titles.
161-
</p>
162-
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
163-
If you would like to use October to sync highlights from the Kobo store, you can do this from the Settings page.
164-
</p>
165-
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
166-
This functionality is disabled by default in order to avoid duplicate highlights for users of the official Readwise Kobo integration.
167-
</p>
168-
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2 italic">
169-
This message won't be shown again once accepted.
170-
</p>
171-
</div>
172-
</div>
173-
</div>
174-
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
175-
<button
176-
type="button"
177-
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
178-
onClick={() => {
179-
// TODO: Only one of these is external so no race condition possible
180-
// but wouldn't hurt to tidy up
181-
setStoreUploadWarningOpen(false)
182-
MarkUploadStorePromptShown()
183-
setUploadStorePromptSeen(true)
184-
}}
185-
>
186-
Understood
187-
</button>
188-
</div>
189-
</Dialog.Panel>
190-
</Transition.Child>
191-
</div>
192-
</div>
193-
</Dialog>
194-
</Transition.Root>
195121
</>
196122
)
197123
}

0 commit comments

Comments
 (0)