Skip to content

Commit

Permalink
[FEAT] Dashboard UI on the web app (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatteoGauthier authored Mar 3, 2023
1 parent ea2af69 commit cc275c9
Show file tree
Hide file tree
Showing 42 changed files with 611 additions and 22 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Active Users Widget

## Todo's

- [ ] Allowedorigins filter
1 change: 1 addition & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# Prisma
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
DATABASE_URL="file:./db.sqlite"
PRISMA_DEBUG="false"

# Next Auth
# You can generate a new secret on the command line with:
Expand Down
12 changes: 8 additions & 4 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"lint": "next lint",
"start": "next start",
"prisma": "cross-env NODE_ENV=${NODE_ENV:-development} dotenv-flow -- prisma",
"prisma:generate": "prisma generate"
"prisma:generate": "npm run prisma generate",
"prisma:studio": "BROWSER=none npm run prisma studio"
},
"dependencies": {
"@next-auth/prisma-adapter": "^1.0.5",
Expand All @@ -20,6 +21,7 @@
"@trpc/react-query": "^10.9.0",
"@trpc/server": "^10.9.0",
"@upstash/redis": "^1.20.0",
"nanoid": "^4.0.1",
"next": "13.1.6",
"next-auth": "^4.19.0",
"react": "18.2.0",
Expand All @@ -28,6 +30,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@types/node": "^18.11.18",
"@types/prettier": "^2.7.2",
"@types/react": "^18.0.26",
Expand All @@ -39,15 +42,16 @@
"dotenv-cli": "^7.0.0",
"dotenv-flow-cli": "^1.0.0",
"eslint": "^8.30.0",
"eslint-config-custom": "workspace:*",
"eslint-config-next": "13.1.6",
"postcss": "^8.4.14",
"prettier": "^2.8.1",
"prettier-plugin-tailwindcss": "^0.2.1",
"prisma": "^4.9.0",
"shiki": "^0.14.1",
"tailwindcss": "^3.2.0",
"typescript": "^4.9.4",
"eslint-config-custom": "workspace:*",
"tsconfig": "workspace:*"
"tsconfig": "workspace:*",
"typescript": "^4.9.4"
},
"ct3aMetadata": {
"initVersion": "7.5.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:
- A unique constraint covering the columns `[key]` on the table `Project` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "key" TEXT;

-- CreateIndex
CREATE UNIQUE INDEX "Project_key_key" ON "Project"("key");
1 change: 1 addition & 0 deletions apps/web/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ model Project {
updatedAt DateTime @updatedAt
name String
key String? @unique
allowedOrigins String[]
Expand Down
Binary file added apps/web/public/fonts/PPMori-ExtraBold.woff
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-ExtraBold.woff2
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-ExtraBoldItalic.woff
Binary file not shown.
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-Extralight.woff
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-Extralight.woff2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-Regular.woff
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-Regular.woff2
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-RegularItalic.woff
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-RegularItalic.woff2
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-SemiBold.woff
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-SemiBold.woff2
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-SemiBoldItalic.woff
Binary file not shown.
Binary file added apps/web/public/fonts/PPMori-SemiBoldItalic.woff2
Binary file not shown.
15 changes: 15 additions & 0 deletions apps/web/src/components/dashboard/NewProjectCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Link from "next/link";
import React from "react";
import PackagePlusIcon from "../svgx/PackagePlusIcon";

export default function NewProjectCard() {
return (
<Link
href="/dashboard/new-project"
className="flex w-full items-center justify-center space-x-2 rounded-md border border-slate-100 bg-slate-50/40 p-4 py-14 text-slate-800 hover:bg-slate-100"
>
<PackagePlusIcon />
<span>Create new project</span>
</Link>
);
}
85 changes: 85 additions & 0 deletions apps/web/src/components/dashboard/ProjectCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Project } from "@prisma/client";
import { useClipboard } from "../../hooks/useClipboard";
import CopyIcon from "../svgx/CopyIcon";

type Props = {
codeSnippet?: {
highlighted: string;
raw: string;
};
project: Project;
};

export default function ProjectCard({ codeSnippet, project }: Props) {
const clipboard = useClipboard();

const cleanedHighlightedCode =
project.key &&
codeSnippet?.highlighted?.replace("%PROJECT_ID%", project.key);
const cleanedRawCode =
project.key && codeSnippet?.raw?.replace("%PROJECT_ID%", project.key);

const copy = () => {
clipboard.copy(cleanedRawCode);
};

return (
<article className="grid grid-cols-6 gap-4 rounded-md border border-slate-100 p-4">
<div className="col-span-2">
<div className="flex items-center justify-between space-x-2">
<div className="flex flex-col items-start">
<span className="rounded-lg font-sans font-medium">
{project.name}
</span>
<span className="py-.5 mt-1 rounded-sm bg-slate-200 px-1 font-mono text-xs font-medium">
{project.key}
</span>
</div>

<div className="inline-flex items-center space-x-1 rounded-sm border border-gray-200 px-2 py-1">
<span className="text-xs text-gray-800">Active</span>
<div className="h-1.5 w-1.5 animate-pulse rounded-full bg-green-500"></div>
</div>
</div>
<div className="mt-4">
<div>
<span className="text-sm text-slate-600">
Visits in the last 30 minutes :{" "}
<span className="font-bold">0</span>
</span>
</div>
<div>
<span className="text-sm text-slate-600">
Total visits : <span className="font-bold">0</span>
</span>
</div>
</div>{" "}
</div>
{cleanedHighlightedCode && (
<div className="col-span-4">
<div className=" rounded-md bg-gradient-to-br from-slate-50 to-slate-200 px-3 py-3">
<h3 className="mb-2 text-lg font-medium leading-none text-slate-700">
Integrate the widget
</h3>
<p>
Add the following code to the <code>&lt;head&gt;</code> tag
</p>
<div className="mt-2 flex space-x-2">
<div
className="code-block"
dangerouslySetInnerHTML={{ __html: cleanedHighlightedCode }}
></div>
<button
onClick={copy}
className="flex items-center space-x-1 rounded-lg bg-slate-800 px-2 py-1 text-white"
>
<span>Copy</span>
<CopyIcon />
</button>
</div>
</div>
</div>
)}
</article>
);
}
24 changes: 24 additions & 0 deletions apps/web/src/components/svgx/CopyIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { SVGProps } from "react";

export default function CopyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
{...props}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<rect width="13" height="13" x="9" y="9" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</g>
</svg>
);
}
24 changes: 24 additions & 0 deletions apps/web/src/components/svgx/PackagePlusIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SVGProps } from "react";

export default function PackagePlusIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
{...props}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="M16 16h6m-3-3v6m2-9V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14M16.5 9.4L7.55 4.24"></path>
<path d="M3.29 7L12 12l8.71-5M12 22V12"></path>
</g>
</svg>
);
}
1 change: 1 addition & 0 deletions apps/web/src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { z } from "zod";
*/
const server = z.object({
DATABASE_URL: z.string().url(),
PRISMA_DEBUG: z.boolean().optional(),
NODE_ENV: z.enum(["development", "test", "production"]),
NEXTAUTH_SECRET:
process.env.NODE_ENV === "production"
Expand Down
34 changes: 34 additions & 0 deletions apps/web/src/hooks/useClipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useState } from "react";

export function useClipboard({ timeout = 2000 } = {}) {
const [error, setError] = useState<Error | null>(null);
const [copied, setCopied] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [copyTimeout, setCopyTimeout] = useState<any>(null);

const handleCopyResult = (value: boolean) => {
clearTimeout(copyTimeout);
setCopyTimeout(setTimeout(() => setCopied(false), timeout));
setCopied(value);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const copy = (valueToCopy: any) => {
if ("clipboard" in navigator) {
navigator.clipboard
.writeText(valueToCopy)
.then(() => handleCopyResult(true))
.catch((err) => setError(err));
} else {
setError(new Error("useClipboard: navigator.clipboard is not supported"));
}
};

const reset = () => {
setCopied(false);
setError(null);
clearTimeout(copyTimeout);
};

return { copy, reset, error, copied };
}
1 change: 1 addition & 0 deletions apps/web/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SessionProvider } from "next-auth/react";
import { api } from "../utils/api";

import "../styles/globals.css";
import "../styles/fonts.css";

const MyApp: AppType<{ session: Session | null }> = ({
Component,
Expand Down
65 changes: 65 additions & 0 deletions apps/web/src/pages/dashboard/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
GetStaticPaths,
GetStaticPropsContext,
InferGetStaticPropsType,
} from "next";
import { prisma } from "../../server/db";

import { serialize } from "superjson";
import { Project } from "@prisma/client";

export const getStaticProps = async (
context: GetStaticPropsContext<{ id: string }>
) => {
const { id } = context.params!;

const project = await prisma.project.findUnique({
where: {
id,
},
});

return {
props: {
project: serialize(project).json as unknown as Project,
id,
},
revalidate: 60,
};
};

export const getStaticPaths: GetStaticPaths = async () => {
const posts = await prisma.project.findMany({
select: {
id: true,
},
});

return {
paths: posts.map((post) => ({
params: {
id: post.id,
},
})),
fallback: "blocking",
};
};

export default function PostViewPage(
props: InferGetStaticPropsType<typeof getStaticProps>
) {
const { project, id } = props;

if (!project) {
return <div>Project not found</div>;
}

return (
<>
<h1>{project.name}</h1>
<em>Created {new Date(project.createdAt).toLocaleDateString()}</em>

<pre>{JSON.stringify(project, null, 4)}</pre>
</>
);
}
31 changes: 31 additions & 0 deletions apps/web/src/pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import shiki from "shiki";
import NewProjectCard from "../../components/dashboard/NewProjectCard";
import ProjectCard from "../../components/dashboard/ProjectCard";
import { prisma } from "../../server/db";
import { api } from "../../utils/api";

export default function DashboardPage() {
const dashboardProjects = api.project.getUserDashboardProjects.useQuery();

const projects = dashboardProjects.data?.projects;
const code = dashboardProjects.data?.code;

return (
<div className="mx-auto max-w-screen-lg">
<h1>Dashboard</h1>

<div className="grid grid-cols-1">
<div>
<h2>Projects</h2>
<div className="flex flex-col space-y-3">
{projects?.map((project) => (
<ProjectCard project={project} key={"dashboard-" + project.id} codeSnippet={code}/>
))}
<NewProjectCard />
</div>
</div>
</div>
</div>
);
}
Loading

0 comments on commit cc275c9

Please sign in to comment.