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

Feature/seo improvement #79

Merged
merged 5 commits into from
Feb 22, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-helmet-async": "^2.0.4",
"react-hook-form": "^7.50.1",
"react-icons": "^5.0.1",
"react-query": "^3.39.3",
Expand Down
19 changes: 13 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,25 @@ import PrivateApp from "./PrivateApp";
import PublicApp from "./PublicApp";
import { MobileWarningTemplate } from "./templates";

import { HelmetProvider } from 'react-helmet-async';

function App() {
const queryClient = useCustomQueryClient();
const { isAuthenticated } = useAuthState();

// Ensure that context is never scoped outside of the current instance of the app
const helmetContext = {};

return (
<>
<QueryClientProvider client={queryClient}>
<MobileWarningTemplate className="h-screen w-screen sm:hidden" />
<div className="hidden h-fit w-fit sm:block">
{isAuthenticated ? <PrivateApp /> : <PublicApp />}
</div>
</QueryClientProvider>
<HelmetProvider context={helmetContext}>
<QueryClientProvider client={queryClient}>
<MobileWarningTemplate className="h-screen w-screen sm:hidden" />
<div className="hidden h-fit w-fit sm:block">
{isAuthenticated ? <PrivateApp /> : <PublicApp />}
</div>
</QueryClientProvider>
</HelmetProvider>
</>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Logo: FC<LogoProps> = ({
return (
<div className={clsx("flex flex-col sm:flex-row items-center justify-center gap-x-7 gap-y-4 text-2xl", className)}>
<img src={logo} className="max-h-16" alt="Ward Analytics" />
<h1 className="text-4xl sm:text-5xl text-center font-semibold font-montserrat text-[#4268ff]">Ward Analytics</h1>
<h1 className="text-4xl sm:text-5xl text-center font-bold font-montserrat text-[#4268ff]">Ward Analytics</h1>
</div>
)
}
Expand Down
42 changes: 42 additions & 0 deletions src/components/common/SEO.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { FC } from "react";
import { Helmet } from "react-helmet-async";

interface SEOProps {
title: string;
description: string;
creator?: string;
type?: string;
}

const SEO: FC<SEOProps> = ({
title,
description,
creator = "Ward Analytics",
type = "website",
}) => {
return (
<Helmet>
{ /* Standard metadata tags */}
<title>{`Ward Analytics - ${title}`}</title>
<meta name='description' content={description} />
{ /* End standard metadata tags */}
{ /* Facebook tags
For more information, visit https://ogp.me/
*/}
<meta property="og:type" content={type} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
{ /* End Facebook tags */}
{ /* Twitter tags
For more information, visit https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary
*/}
<meta name="twitter:creator" content={creator} />
<meta name="twitter:card" content={type} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
{ /* End Twitter tags */}
</Helmet>
)
}

export default SEO;
94 changes: 49 additions & 45 deletions src/templates/BillingTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
useCustomerPortalUrl,
} from "../services/stripe";
import { Colors } from "../utils/colors";
import SEO from "../components/common/SEO";

interface PlanProps {
isPro: boolean;
Expand Down Expand Up @@ -261,54 +262,57 @@ const BillingTemplate: FC = () => {
}, [customerPortalUrl]);

return (
<div className="mx-14 my-10 flex w-full flex-col gap-y-5">
<h3 className="flex flex-row gap-x-2 text-xl font-semibold text-gray-700">
<CreditCardIcon className="mt-0.5 inline-block h-7 w-7 text-gray-400" />
Plan & Billing
</h3>
<div className="h-[1px] w-full bg-gray-200" />
<>
<SEO title="Billing" description="Manage your subscription and billing" />
<div className="mx-14 my-10 flex w-full flex-col gap-y-5">
<h3 className="flex flex-row gap-x-2 text-xl font-semibold text-gray-700">
<CreditCardIcon className="mt-0.5 inline-block h-7 w-7 text-gray-400" />
Plan & Billing
</h3>
<div className="h-[1px] w-full bg-gray-200" />

{
// TODO: Replace for a loading component to standardize the loading state
{
// TODO: Replace for a loading component to standardize the loading state

<>
<span className="flex flex-row justify-between">
<h3 className="flex flex-row items-center gap-x-2.5 text-xl font-semibold text-gray-700">
Plan:
{isLoading ? (
<div className="h-7 w-16 animate-pulse rounded-md bg-gray-200" />
) : isPro ? (
<p className=" flex flex-row items-center gap-x-1.5 rounded-lg bg-blue-100 px-2 py-1 font-bold text-blue-500 shadow-lg shadow-blue-200/50 ring-1 ring-inset ring-blue-300">
<RocketLaunchIcon className="mt-0.5 inline-block h-5 w-5 text-blue-500" />
Pro
</p>
) : (
<p className="text-gray-500">Free</p>
<>
<span className="flex flex-row justify-between">
<h3 className="flex flex-row items-center gap-x-2.5 text-xl font-semibold text-gray-700">
Plan:
{isLoading ? (
<div className="h-7 w-16 animate-pulse rounded-md bg-gray-200" />
) : isPro ? (
<p className=" flex flex-row items-center gap-x-1.5 rounded-lg bg-blue-100 px-2 py-1 font-bold text-blue-500 shadow-lg shadow-blue-200/50 ring-1 ring-inset ring-blue-300">
<RocketLaunchIcon className="mt-0.5 inline-block h-5 w-5 text-blue-500" />
Pro
</p>
) : (
<p className="text-gray-500">Free</p>
)}
</h3>
{isPro && (
<BigButton
text="Manage Subscription"
onClick={() => setManageSubscriptionClicked(true)}
Icon={CreditCardSmallIcon}
/>
)}
</h3>
{isPro && (
<BigButton
text="Manage Subscription"
onClick={() => setManageSubscriptionClicked(true)}
Icon={CreditCardSmallIcon}
/>
)}
</span>
<span className="flex flex-row justify-center gap-x-5">
<>
<DiscoverPlan isPro={isPro} />
<ProPlan
key={subscription?.id}
isPro={isPro}
userID={userID}
subscription={subscription}
/>
<EnterprisePlan />
</>
</span>
</>
}
</div>
</span>
<span className="flex flex-row justify-center gap-x-5">
<>
<DiscoverPlan isPro={isPro} />
<ProPlan
key={subscription?.id}
isPro={isPro}
userID={userID}
subscription={subscription}
/>
<EnterprisePlan />
</>
</span>
</>
}
</div>
</>
);
};

Expand Down
16 changes: 10 additions & 6 deletions src/templates/SavedGraphTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { Graph } from "../components/graph/Graph";
import { PersonalGraph } from "../services/firestore/user/graph_saving";
import useAuthState from "../hooks/useAuthState";
import SEO from "../components/common/SEO";

const SavedGraphTemplate: FC = () => {
const { user } = useAuthState();
Expand Down Expand Up @@ -49,12 +50,15 @@ const SavedGraphTemplate: FC = () => {
if (!graph) return null;

return (
<Graph
initialAddresses={graph.data.addresses}
initialPaths={graph.data.edges}
onAutoSave={saveGraph}
key={graph.uid}
/>
<>
<SEO title={graph.name} description="View and edit your saved graph" />
<Graph
initialAddresses={graph.data.addresses}
initialPaths={graph.data.edges}
onAutoSave={saveGraph}
key={graph.uid}
/>
</>
);
};

Expand Down
52 changes: 28 additions & 24 deletions src/templates/SavedGraphsTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { PlusCircleIcon } from "@heroicons/react/20/solid";
import { useNavigate } from "react-router-dom";
import CreateGraphDialog from "../components/common/CreateGraphDialog";
import DeleteGraphDialog from "../components/common/DeleteGraphDialog";
import SEO from "../components/common/SEO";

const GraphCard: FC<{ graph: PersonalGraph }> = ({ graph }) => {
const navigate = useNavigate();
Expand Down Expand Up @@ -60,33 +61,36 @@ const SavedGraphsTemplate: FC = () => {
const [isCreateGraphDialogOpen, setIsCreateGraphDialogOpen] = useState(false);

return (
<div className="mx-14 my-10 flex w-full flex-col gap-y-5">
<h3 className="flex flex-row justify-between text-xl font-semibold text-gray-700">
<div className="flex flex-row items-center gap-2">
<ShareIcon className="mt-0.5 inline-block h-7 w-7 text-gray-400" />
Saved Graphs
<>
<SEO title="Saved Graphs" description="View and manage your saved graphs" />
<div className="mx-14 my-10 flex w-full flex-col gap-y-5">
<h3 className="flex flex-row justify-between text-xl font-semibold text-gray-700">
<div className="flex flex-row items-center gap-2">
<ShareIcon className="mt-0.5 inline-block h-7 w-7 text-gray-400" />
Saved Graphs
</div>
<BigButton
text="New graph"
onClick={() => {
setIsCreateGraphDialogOpen(true);
}}
Icon={PlusCircleIcon}
/>
</h3>
<div className="h-[1px] w-full bg-gray-200" />
<div className="scrollbar overflow-x-hidden overflow-y-scroll">
<div className="grid auto-rows-auto grid-cols-3 gap-4 pr-3">
{graphs.map((graph) => (
<GraphCard graph={graph} key={graph.uid} />
))}
</div>
</div>
<BigButton
text="New graph"
onClick={() => {
setIsCreateGraphDialogOpen(true);
}}
Icon={PlusCircleIcon}
<CreateGraphDialog
isOpen={isCreateGraphDialogOpen}
setOpen={setIsCreateGraphDialogOpen}
/>
</h3>
<div className="h-[1px] w-full bg-gray-200" />
<div className="scrollbar overflow-x-hidden overflow-y-scroll">
<div className="grid auto-rows-auto grid-cols-3 gap-4 pr-3">
{graphs.map((graph) => (
<GraphCard graph={graph} key={graph.uid} />
))}
</div>
</div>
<CreateGraphDialog
isOpen={isCreateGraphDialogOpen}
setOpen={setIsCreateGraphDialogOpen}
/>
</div>
</>
);
};

Expand Down
64 changes: 34 additions & 30 deletions src/templates/UnsavedGraphTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useParams } from "react-router-dom";
import { SharableGraph, getSharableGraph } from "../services/firestore/graph_sharing";

import { Transition } from "@headlessui/react";
import SEO from "../components/common/SEO";
import { Graph } from "../components/graph/Graph";
import LandingPage from "../components/graph/landing_page/LandingPage";

Expand Down Expand Up @@ -81,36 +82,39 @@ const UnsavedGraphTemplate: FC<UnsavedGraphTemplateProps> = ({
if (loading) return null;

return (
<div className="h-full overflow-hidden">
<Transition
show={initialAddresses.length === 0 && showLandingPage}
appear={true}
leave="transition-all duration-500"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-50"
className="absolute flex h-full w-full flex-col items-center justify-center"
>
<LandingPage
setSearchedAddress={(address: string) => {
setGraph({ addresses: [address], edges: [] });
}}
/>
</Transition>
<Transition
show={initialAddresses.length > 0 || !showLandingPage}
appear={true}
enter="transition-all duration-500 delay-500"
enterFrom="opacity-0 scale-150"
enterTo="opacity-100 scale-100"
className="h-full w-full"
>
<Graph
initialAddresses={initialAddresses}
initialPaths={initialPaths}
onLocalSave={saveGraph}
/>
</Transition>
</div>
<>
<SEO title="Graph" description="Creating the next-gen of crypto compliance" />
<div className="h-full overflow-hidden">
<Transition
show={initialAddresses.length === 0 && showLandingPage}
appear={true}
leave="transition-all duration-500"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-50"
className="absolute flex h-full w-full flex-col items-center justify-center"
>
<LandingPage
setSearchedAddress={(address: string) => {
setGraph({ addresses: [address], edges: [] });
}}
/>
</Transition>
<Transition
show={initialAddresses.length > 0 || !showLandingPage}
appear={true}
enter="transition-all duration-500 delay-500"
enterFrom="opacity-0 scale-150"
enterTo="opacity-100 scale-100"
className="h-full w-full"
>
<Graph
initialAddresses={initialAddresses}
initialPaths={initialPaths}
onLocalSave={saveGraph}
/>
</Transition>
</div >
</>
);
};

Expand Down
Loading
Loading