Skip to content

Commit 2d6be77

Browse files
committed
finish landing page
1 parent be013c2 commit 2d6be77

File tree

7 files changed

+429
-92
lines changed

7 files changed

+429
-92
lines changed

jupyterlab_leetcode/handlers/cookie_handler.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,23 @@ def get(self):
4343
self.finish(json.dumps({"message": f"Unsupported browser: {browser}"}))
4444
return
4545

46-
cj = BROWSER_COOKIE_METHOD_MAP[browser](domain_name="leetcode.com")
46+
try:
47+
cj = BROWSER_COOKIE_METHOD_MAP[browser](domain_name="leetcode.com")
48+
except browser_cookie3.BrowserCookieError as e:
49+
self.set_status(418)
50+
self.finish(
51+
json.dumps(
52+
{
53+
"message": "Failed to retrieve cookies. Maybe not installed the browser?"
54+
}
55+
)
56+
)
57+
return
58+
except Exception as e:
59+
self.set_status(418)
60+
self.finish(json.dumps({"message": str(e.args)}))
61+
return
62+
4763
cookie_session = first(cj, lambda c: c.name == "LEETCODE_SESSION")
4864
cookie_csrf = first(cj, lambda c: c.name == "csrftoken")
4965
exist = bool(cookie_session and cookie_csrf)

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030
"url": "https://github.com/Sorosliu1029/jupyterlab-leetcode.git"
3131
},
3232
"scripts": {
33-
"build": "jlpm build:lib && jlpm build:labextension:dev",
34-
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
33+
"build": "jlpm build:lib && jlpm copy && jlpm build:labextension:dev",
34+
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm copy && jlpm build:labextension",
3535
"build:labextension": "jupyter labextension build .",
3636
"build:labextension:dev": "jupyter labextension build --development True .",
3737
"build:lib": "tsc --sourceMap",
3838
"build:lib:prod": "tsc",
39+
"copy": "copyfiles -u 1 src/**/*.css lib/",
3940
"clean": "jlpm clean:lib",
4041
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
4142
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
@@ -65,8 +66,7 @@
6566
"@jupyterlab/settingregistry": "^4.0.0",
6667
"@mantine/core": "^7.17.8",
6768
"@mantine/hooks": "^7.17.8",
68-
"@tabler/icons-react": "^3.34.0",
69-
"bowser": "^2.11.0"
69+
"@tabler/icons-react": "^3.34.0"
7070
},
7171
"devDependencies": {
7272
"@jupyterlab/builder": "^4.0.0",
@@ -77,6 +77,7 @@
7777
"@types/react-addons-linked-state-mixin": "^0.14.22",
7878
"@typescript-eslint/eslint-plugin": "^6.1.0",
7979
"@typescript-eslint/parser": "^6.1.0",
80+
"copyfiles": "^2.4.1",
8081
"css-loader": "^6.7.1",
8182
"eslint": "^8.36.0",
8283
"eslint-config-prettier": "^8.8.0",

src/components/BrowserCookie.tsx

Lines changed: 129 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,100 @@
11
import React, { useEffect, useState } from 'react';
22
import { Notification } from '@jupyterlab/apputils';
3+
import {
4+
IconChevronDown,
5+
IconBrandChrome,
6+
IconBrandFirefox,
7+
IconBrandEdge,
8+
IconBrandSafari,
9+
IconBrandOpera,
10+
IconBrandVivaldi,
11+
IconBrandArc,
12+
IconWorldWww
13+
} from '@tabler/icons-react';
14+
import { Button, Menu, useMantineTheme } from '@mantine/core';
315
import { getCookie } from '../services/cookie';
4-
import Bowser from 'bowser';
516

6-
const BrowserCookie: React.FC<{
7-
setCookieLoggedIn: (b: string) => void;
8-
}> = ({ setCookieLoggedIn }) => {
9-
const browsers = [
10-
'Chrome',
11-
'Firefox',
12-
'Safari',
13-
'Edge',
14-
'Opera',
15-
'Brave',
16-
'Vivaldi',
17-
'Chromium',
18-
'Arc',
19-
'LibreWolf',
20-
'Opera GX'
21-
];
17+
const BROWSERS = [
18+
{
19+
name: 'Chrome',
20+
icon: (color: string) => (
21+
<IconBrandChrome size={16} color={color} stroke={1.5} />
22+
)
23+
},
24+
{
25+
name: 'Firefox',
26+
icon: (color: string) => (
27+
<IconBrandFirefox size={16} color={color} stroke={1.5} />
28+
)
29+
},
30+
{
31+
name: 'Safari',
32+
icon: (color: string) => (
33+
<IconBrandSafari size={16} color={color} stroke={1.5} />
34+
)
35+
},
36+
{
37+
name: 'Edge',
38+
icon: (color: string) => (
39+
<IconBrandEdge size={16} color={color} stroke={1.5} />
40+
)
41+
},
42+
{
43+
name: 'Opera',
44+
icon: (color: string) => (
45+
<IconBrandOpera size={16} color={color} stroke={1.5} />
46+
)
47+
},
48+
{
49+
name: 'Brave',
50+
icon: (color: string) => (
51+
<IconWorldWww size={16} color={color} stroke={1.5} />
52+
)
53+
},
54+
{
55+
name: 'Vivaldi',
56+
icon: (color: string) => (
57+
<IconBrandVivaldi size={16} color={color} stroke={1.5} />
58+
)
59+
},
60+
{
61+
name: 'Chromium',
62+
icon: (color: string) => (
63+
<IconBrandChrome size={16} color={color} stroke={1.5} />
64+
)
65+
},
66+
{
67+
name: 'Arc',
68+
icon: (color: string) => (
69+
<IconBrandArc size={16} color={color} stroke={1.5} />
70+
)
71+
},
72+
{
73+
name: 'LibreWolf',
74+
icon: (color: string) => (
75+
<IconWorldWww size={16} color={color} stroke={1.5} />
76+
)
77+
},
78+
{
79+
name: 'Opera GX',
80+
icon: (color: string) => (
81+
<IconBrandOpera size={16} color={color} stroke={1.5} />
82+
)
83+
}
84+
];
2285

23-
const normalizeBrowserName = (name: string) =>
24-
name.toLowerCase().replace(/\s+/g, '_');
86+
const normalizeBrowserName = (name: string) =>
87+
name.toLowerCase().replace(/\s+/g, '_');
2588

89+
const BrowserCookie: React.FC<{
90+
className?: string;
91+
setCookieLoggedIn: (b: string) => void;
92+
}> = ({ className, setCookieLoggedIn }) => {
2693
const [browser, setBrowser] = useState('');
2794
const [checked, setChecked] = useState(false);
2895

29-
// set browser value by detecting current browser
3096
useEffect(() => {
31-
const browserName = Bowser.getParser(
32-
window.navigator.userAgent
33-
).getBrowserName(true);
34-
if (browserName) {
35-
const firstMatch = browsers.find(b =>
36-
new RegExp(b, 'i').test(browserName)
37-
);
38-
if (firstMatch) {
39-
setBrowser(normalizeBrowserName(firstMatch));
40-
}
41-
}
42-
}, []);
43-
44-
useEffect(() => {
45-
if (checked) {
46-
setCookieLoggedIn(browser);
47-
}
48-
}, [checked, setCookieLoggedIn]);
49-
50-
const checkCookie = () => {
5197
if (!browser) {
52-
Notification.error('Please select a browser.', { autoClose: 3000 });
5398
return;
5499
}
55100
if (browser === 'safari') {
@@ -62,37 +107,56 @@ const BrowserCookie: React.FC<{
62107

63108
getCookie(browser)
64109
.then(resp => {
110+
if (!resp['checked']) {
111+
Notification.error(
112+
`Failed to check cookie for ${browser}. Please ensure you are logged in to LeetCode in this browser.`,
113+
{ autoClose: 3000 }
114+
);
115+
}
65116
setChecked(resp['checked']);
66117
})
67118
.catch(e => Notification.error(e.message, { autoClose: 3000 }));
68-
};
119+
}, [browser]);
120+
121+
useEffect(() => {
122+
if (checked) {
123+
setCookieLoggedIn(browser);
124+
}
125+
}, [checked]);
69126

127+
const theme = useMantineTheme();
70128
return (
71-
<div>
72-
<label htmlFor="browser-selector">
73-
Choose your browser that has LeetCode logged in:
74-
</label>
75-
<select
76-
id="browser-selector"
77-
required
78-
value={browser}
79-
onChange={e => setBrowser(e.target.value)}
80-
>
81-
<option value="" disabled>
82-
Select a browser
83-
</option>
84-
{browsers.map(browser => (
85-
<option
86-
key={browser.toLowerCase()}
87-
value={normalizeBrowserName(browser)}
129+
<Menu
130+
transitionProps={{ transition: 'pop-top-right' }}
131+
position="bottom-end"
132+
width={220}
133+
withinPortal
134+
radius="md"
135+
>
136+
<Menu.Target>
137+
<Button
138+
rightSection={<IconChevronDown size={18} stroke={1.5} />}
139+
pr={12}
140+
radius="md"
141+
size="md"
142+
className={className}
143+
>
144+
Load from browser
145+
</Button>
146+
</Menu.Target>
147+
<Menu.Dropdown>
148+
<Menu.Label>With LeetCode logged in</Menu.Label>
149+
{BROWSERS.map(({ name, icon }, i) => (
150+
<Menu.Item
151+
key={name}
152+
leftSection={icon(theme.colors.blue[6])}
153+
onClick={() => setBrowser(normalizeBrowserName(name))}
88154
>
89-
{browser}
90-
</option>
155+
{name}
156+
</Menu.Item>
91157
))}
92-
</select>
93-
<button onClick={checkCookie}>Check</button>
94-
<p>Checked: {checked ? 'Yes' : 'No'}</p>
95-
</div>
158+
</Menu.Dropdown>
159+
</Menu>
96160
);
97161
};
98162

src/components/LandingPage.tsx

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,79 @@
11
import React from 'react';
22
import BrowserCookie from './BrowserCookie';
33

4+
import { Button, Container, Group, Text, Tooltip, Anchor } from '@mantine/core';
5+
import { IconBrandGithub, IconBrandLinkedin } from '@tabler/icons-react';
6+
import classes from '../styles/LandingPage.module.css';
7+
8+
const LeetCdoeGradient = { from: '#FEA512', to: '#FFDB01' };
9+
410
const LandingPage: React.FC<{
511
setCookieLoggedIn: (b: string) => void;
612
}> = ({ setCookieLoggedIn }) => {
713
const options: JSX.Element[] = [
8-
<BrowserCookie setCookieLoggedIn={setCookieLoggedIn} />
14+
<BrowserCookie
15+
setCookieLoggedIn={setCookieLoggedIn}
16+
className={classes.control}
17+
/>,
18+
<Tooltip label="Not implemented yet, contributions are welcome!">
19+
<Button
20+
size="md"
21+
className={classes.control}
22+
variant="filled"
23+
data-disabled
24+
onClick={e => e.preventDefault()}
25+
leftSection={<IconBrandGithub size={20} />}
26+
>
27+
GitHub Login
28+
</Button>
29+
</Tooltip>,
30+
<Tooltip label="Not implemented yet, contributions are welcome!">
31+
<Button
32+
size="md"
33+
className={classes.control}
34+
variant="filled"
35+
data-disabled
36+
onClick={e => e.preventDefault()}
37+
leftSection={<IconBrandLinkedin size={20} />}
38+
>
39+
LinkedIn Login
40+
</Button>
41+
</Tooltip>
942
];
43+
1044
return (
11-
<div>
12-
<p>Welcome to JupyterLab LeetCode Widget.</p>
13-
<p>
14-
For this plugin to work, you may choose one of these {options.length}{' '}
15-
methods to allow this plugin to log into LeetCode.
16-
</p>
17-
{...options}
45+
<div className={classes.wrapper}>
46+
<Container size={700} className={classes.inner}>
47+
<h1 className={classes.title}>
48+
Welcome to{' '}
49+
<Text
50+
component="span"
51+
variant="gradient"
52+
gradient={LeetCdoeGradient}
53+
inherit
54+
>
55+
JupyterLab LeetCode
56+
</Text>{' '}
57+
plugin
58+
</h1>
59+
60+
<Text className={classes.description} color="dimmed">
61+
For this plugin to work, you may choose one of these {options.length}{' '}
62+
methods to allow this plugin to{' '}
63+
<Anchor
64+
href="https://leetcode.com/accounts/login/"
65+
target="_blank"
66+
variant="gradient"
67+
gradient={LeetCdoeGradient}
68+
className={classes.description}
69+
>
70+
log into LeetCode
71+
</Anchor>
72+
.
73+
</Text>
74+
75+
<Group className={classes.controls}>{...options}</Group>
76+
</Container>
1877
</div>
1978
);
2079
};

0 commit comments

Comments
 (0)