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

Add auth + project analytics #395

Merged
merged 18 commits into from
Sep 23, 2024
1 change: 1 addition & 0 deletions app/common/models/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface UserSettings {
id?: string;
enableAnalytics?: boolean;
ideType?: IdeType;
signInMethod?: string;
}

export interface ProjectsCache {
Expand Down
10 changes: 7 additions & 3 deletions app/electron/main/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Analytics {
this.id = settings.id;
if (!this.id) {
this.id = nanoid();
PersistentStorage.USER_SETTINGS.write({ enableAnalytics: enable, id: this.id });
PersistentStorage.USER_SETTINGS.update({ enableAnalytics: enable, id: this.id });
}

if (enable) {
Expand All @@ -54,12 +54,16 @@ class Analytics {
this.track('disable analytics');
this.disable();
}
PersistentStorage.USER_SETTINGS.write({ enableAnalytics: enable, id: this.id });
PersistentStorage.USER_SETTINGS.update({ enableAnalytics: enable, id: this.id });
}

private enable() {
try {
this.mixpanel = Mixpanel.init(import.meta.env.VITE_MIXPANEL_TOKEN || '');
const settings = PersistentStorage.USER_METADATA.read();
if (settings) {
this.identify(settings);
}
} catch (error) {
console.warn('Error initializing Mixpanel:', error);
console.warn('No Mixpanel client, analytics will not be collected');
Expand Down Expand Up @@ -96,7 +100,7 @@ class Analytics {
}

public signOut() {
PersistentStorage.USER_SETTINGS.write({ id: undefined });
PersistentStorage.USER_SETTINGS.update({ id: undefined });
}
}

Expand Down
4 changes: 2 additions & 2 deletions app/electron/main/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function handleAuthCallback(url: string) {
}

const authTokens = getToken(url);
PersistentStorage.AUTH_TOKENS.write(authTokens);
PersistentStorage.AUTH_TOKENS.replace(authTokens);

if (!supabase) {
throw new Error('No backend connected');
Expand All @@ -32,7 +32,7 @@ export async function handleAuthCallback(url: string) {
}

const userMetadata = getUserMetadata(user);
PersistentStorage.USER_METADATA.write(userMetadata);
PersistentStorage.USER_METADATA.replace(userMetadata);

analytics.identify(userMetadata);
emitAuthEvent();
Expand Down
1 change: 0 additions & 1 deletion app/electron/main/storage/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ class ImageStorage {
try {
if (existsSync(filePath)) {
unlinkSync(filePath);
console.log(`Image deleted successfully: ${filePath}`);
return true;
}
console.log(`Image not found: ${filePath}`);
Expand Down
2 changes: 1 addition & 1 deletion app/electron/main/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class PersistentStorage<T> {
}
}

write(value: T) {
replace(value: T) {
try {
this.encrypted ? this.writeEncrypted(value) : this.writeUnencrypted(value);
} catch (e) {
Expand Down
47 changes: 29 additions & 18 deletions app/src/components/AppBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { observer } from 'mobx-react-lite';
import { Button } from '../ui/button';
import UpdateButton from './UpdateButton';
import { Links } from '/common/constants';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';

const AppBar = observer(() => {
const routeManager = useRouteManager();
Expand All @@ -13,24 +14,34 @@ const AppBar = observer(() => {
className={`flex flex-row items-center pl-20 h-10 ${routeManager.route === Route.SIGN_IN ? 'bg-transparent' : 'bg-bg-active border-b'}`}
>
<div className="appbar w-full h-full"></div>
<Button
size="sm"
variant="ghost"
onClick={() => {
window.open(Links.DISCORD, '_blank');
}}
>
<DiscordLogoIcon />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => {
window.open(Links.GITHUB, '_blank');
}}
>
<GitHubLogoIcon />
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
onClick={() => {
window.open(Links.DISCORD, '_blank');
}}
>
<DiscordLogoIcon />
</Button>
</TooltipTrigger>
<TooltipContent>Join our Discord</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
onClick={() => {
window.open(Links.GITHUB, '_blank');
}}
>
<GitHubLogoIcon />
</Button>
</TooltipTrigger>
<TooltipContent>Visit our GitHub</TooltipContent>
</Tooltip>
<div className="flex mr-2 gap-2">
<div className="flex ml-1 rounded-sm bg-gradient-to-r p-[1px] from-[#6EE7B7] via-[#3B82F6] to-[#9333EA]">
<Button
Expand Down
4 changes: 3 additions & 1 deletion app/src/lib/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeAutoObservable } from 'mobx';
import { sendAnalytics } from '../utils';
import { APP_SCHEMA, MainChannels } from '/common/constants';
import { UserMetadata } from '/common/models/settings';
import supabase from '/common/supabase';
Expand Down Expand Up @@ -56,13 +57,14 @@ export class AuthManager {
}

window.api.invoke(MainChannels.OPEN_EXTERNAL_WINDOW, data.url);
sendAnalytics('sign in', { provider });
}

signOut() {
if (!supabase) {
throw new Error('No backend connected');
}

sendAnalytics('sign out');
supabase.auth.signOut();
window.api.invoke(MainChannels.SIGN_OUT);
}
Expand Down
8 changes: 8 additions & 0 deletions app/src/lib/editor/engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ export class EditorEngine {
return null;
}

const hasContent = await webview.executeJavaScript(
`document.body.innerText.trim().length > 0 || document.body.children.length > 0 `,
);
if (!hasContent) {
console.error('No content found in webview');
return null;
}

const imageName = `${name}-preview.png`;
const image: NativeImage = await webview.capturePage();
const path: string | null = await window.api.invoke(MainChannels.SAVE_IMAGE, {
Expand Down
2 changes: 2 additions & 0 deletions app/src/lib/projects/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { makeAutoObservable } from 'mobx';
import { nanoid } from 'nanoid';
import { sendAnalytics } from '../utils';
import { MainChannels } from '/common/constants';
import { Project } from '/common/models/project';
import { AppState, ProjectsCache } from '/common/models/settings';
Expand Down Expand Up @@ -71,6 +72,7 @@ export class ProjectsManager {
this.project = null;
}
this.projects = this.projectList.filter((p) => p.id !== project.id);
sendAnalytics('delete project', { url: project.url, id: project.id });
}

get project() {
Expand Down
3 changes: 3 additions & 0 deletions app/src/lib/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class RouteManager {
}

set route(newRoute: Route) {
if (newRoute === this.currentRoute) {
return;
}
this.currentRoute = newRoute;
sendAnalytics('navigate', { route: newRoute });
}
Expand Down
19 changes: 9 additions & 10 deletions app/src/routes/editor/TopBar/ProjectSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,24 @@ const ProjectBreadcrumb = observer(() => {
const projectsManager = useProjectsManager();

async function handleReturn() {
saveScreenshot();
await saveScreenshot();
projectsManager.project = null;
}

function saveScreenshot() {
async function saveScreenshot() {
const project = projectsManager.project;
if (!project) {
console.error('No project selected');
return;
}
const projectId = project.id;
editorEngine.takeScreenshot(projectId).then((imageName) => {
if (!imageName) {
console.error('Failed to take screenshot');
return;
}
project.previewImg = imageName;
projectsManager.updateProject(project);
});
const imageName = await editorEngine.takeScreenshot(projectId);
if (!imageName) {
console.error('Failed to take screenshot');
return;
}
project.previewImg = imageName;
projectsManager.updateProject(project);
}

return (
Expand Down
12 changes: 6 additions & 6 deletions app/src/routes/editor/WebviewArea/Frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,13 @@ const Frame = observer(
<GestureScreen webviewRef={webviewRef} setHovered={setHovered} />
{domFailed && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-gradient-to-t from-gray-200/40 via-gray-500/40 to-gray-600/40 border-gray-500 border-[0.5px] space-y-4 rounded-xl">
<p className="text-active text-title1">
Run your React app to start editing
<p className="text-active text-title1 text-center">
{'Your React app is not running'}
</p>
<p className="text-text text-title2 text-center">
{
"Make sure Onlook is installed on your app with 'npx onlook setup'"
}
<p className="text-text text-title2 leading-normal text-center">
{`Make sure that your app is running in your terminal`}
<br />
{`and that you're pointing the above browser URL to the correct location`}
</p>
<Button
variant={'link'}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Card } from '@/components/ui/card';
import { DownloadIcon, FilePlusIcon } from '@radix-ui/react-icons';
import { CreateMethod } from '../..';
import { CreateMethod } from '@/routes/projects/helpers';

export const ChooseMethod = ({
setCreateMethod,
Expand Down
13 changes: 9 additions & 4 deletions app/src/routes/projects/ProjectsTab/Create/Load/Name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { StepProps } from '..';
import { getRandomPlaceholder } from '../../../helpers';
import { MainChannels } from '/common/constants';

export const LoadNameProject = ({
props: { projectData, currentStep, setProjectData, totalSteps, prevStep, nextStep },
Expand All @@ -23,13 +24,17 @@ export const LoadNameProject = ({
name,
});
}

function goBack() {
window.api.invoke(MainChannels.VERIFY_PROJECT, projectData.folderPath);
prevStep();
}

return (
<Card className="w-[30rem]">
<CardHeader>
<CardTitle>{'Let’s name your project'}</CardTitle>
<CardDescription>
{"We'll install the necessary dependencies for you"}
</CardDescription>
<CardDescription>{'You can always change this later'}</CardDescription>
</CardHeader>
<CardContent className="h-24 flex items-center w-full">
<div className="flex flex-col w-full gap-2">
Expand All @@ -45,7 +50,7 @@ export const LoadNameProject = ({
<CardFooter className="text-sm">
<p>{`${currentStep + 1} of ${totalSteps}`}</p>
<div className="flex ml-auto gap-2">
<Button type="button" onClick={prevStep} variant="ghost">
<Button type="button" onClick={goBack} variant="ghost">
Back
</Button>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@ import {
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { getNameFromPath } from '@/routes/projects/helpers';
import { MinusCircledIcon } from '@radix-ui/react-icons';
import { StepProps } from '..';
import { MainChannels } from '/common/constants';
import { capitalizeFirstLetter } from '/common/helpers';

export const LoadSelectFolder = ({
props: { projectData, setProjectData, currentStep, totalSteps, prevStep, nextStep },
}: {
props: StepProps;
}) => {
function getNameFromPath(path: string) {
return capitalizeFirstLetter(path.split('/').pop() || '');
}
async function pickProjectFolder() {
const path = (await window.api.invoke(MainChannels.PICK_COMPONENTS_DIRECTORY)) as
| string
Expand Down Expand Up @@ -56,7 +53,7 @@ export const LoadSelectFolder = ({
<div className="flex flex-col text-sm gap-1 break-all">
<p className="text-regularPlus">{projectData.name}</p>
<button
className="hover:underline text-mini text-text"
className="hover:underline text-mini text-text text-start"
onClick={handleClickPath}
>
{projectData.folderPath}
Expand Down
37 changes: 30 additions & 7 deletions app/src/routes/projects/ProjectsTab/Create/Load/SetUrl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,40 @@ import {
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useState } from 'react';
import { StepProps } from '..';

export const LoadSetUrl = ({
props: { projectData, setProjectData, currentStep, totalSteps, prevStep, nextStep },
}: {
props: StepProps;
}) => {
const [inputValue, setInputValue] = useState<string>(projectData.url || '');
const [error, setError] = useState<string | null>(null);

function handleUrlInput(e: React.FormEvent<HTMLInputElement>) {
setInputValue(e.currentTarget.value);
if (!validateUrl(e.currentTarget.value)) {
setError('Please use a valid URL');
return;
} else {
setError(null);
}
setProjectData({
...projectData,
url: e.currentTarget.value,
});
}

function validateUrl(url: string): boolean {
try {
const parsedUrl = new URL(url);
return ['http:', 'https:'].includes(parsedUrl.protocol);
} catch (e) {
return false;
}
}

return (
<Card className="w-[30rem]">
<CardHeader>
Expand All @@ -26,16 +53,12 @@ export const LoadSetUrl = ({
<div className="flex flex-col w-full gap-2">
<Label htmlFor="text">Local Url</Label>
<Input
value={projectData.url || ''}
value={inputValue}
type="text"
placeholder="http://localhost:3000"
onInput={(e) =>
setProjectData({
...projectData,
url: e.currentTarget.value,
})
}
onInput={handleUrlInput}
/>
<p className="text-red-500 text-sm">{error || ''}</p>
</div>
</CardContent>
<CardFooter className="text-sm">
Expand Down
Loading