Skip to content

Commit

Permalink
feat: ticket logic
Browse files Browse the repository at this point in the history
  • Loading branch information
yjose committed Feb 2, 2024
1 parent c974ddc commit 7c2e6d4
Show file tree
Hide file tree
Showing 12 changed files with 337 additions and 40 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the

## Setup

1. Create a firebase app and with a firestore database.
1. Create a Firebase app with a Firestore database.

2. Go to firebase project setting > Service account and generate a new private key,Download the json file and add the copy the following environment variables 👇
2. Go to Firebase project settings > Service Account and generate a new private key. Download the JSON file and copy the following environment variables: 👇

```
#.env.development.local
Expand All @@ -26,12 +26,12 @@ FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=
```

3. Create a Github auth application and add `https://yourwebsite.com/api/github-auth` as redirect url. Add you github client id and the the secret key to your environment variables:
3. Create a GitHub authentication application and add `https://yourwebsite.com/api/github-auth` as the redirect URL. Add your GitHub client ID and the secret key to your environment variables.

```
NEXT_PUBLIC_GITHUB_OAUTH_CLIENT_ID=
GITHUB_OAUTH_CLIENT_SECRET=
```

4. Configure ticket generation by updating the `api/og.tsx` file.
4. Configure ticket generation by updating the `app/ticket/og/route.tsx` file.
108 changes: 108 additions & 0 deletions app/github-auth/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { type NextRequest } from "next/server";
import * as qs from "qs";
import { saveUser, User } from "@/utils/db";
import { redirect } from "next/navigation";

export const dynamic = "force-dynamic"; // defaults to auto

export async function GET(request: NextRequest) {
// first step
// get user info using github api
const searchParams = request.nextUrl.searchParams;
const code = searchParams.get("code");

if (!code) {
// This happens when user cancelled the authentication.
// In this case, we send an empty message which indicates no data available.

return Response.json({
status: 400,
code: "github_issue",
description: "Github redirect code not found, please try again",
});
}

const q = qs.stringify({
client_id: process.env.NEXT_PUBLIC_GITHUB_OAUTH_CLIENT_ID,
client_secret: process.env.GITHUB_OAUTH_CLIENT_SECRET,
code: code,
});

const accessTokenRes = await fetch(
`https://github.com/login/oauth/access_token?${q}`,
{
method: "POST",
headers: {
Accept: "application/json",
},
}
);

if (!accessTokenRes.ok) {
console.error(
`Failed to get access token: ${
accessTokenRes.status
} ${await accessTokenRes.text()}`
);

return Response.json({
status: 400,
code: "github_issue",
description: "Error generating access token, please try again ",
});
}

const { access_token: accessToken } = await accessTokenRes.json();

const userRes = await fetch("https://api.github.com/user", {
headers: {
Authorization: `bearer ${accessToken as string}`,
},
});

if (!userRes.ok) {
console.error(
`Failed to get GitHub user: ${userRes.status} ${await userRes.text()}`
);
return Response.json({
status: 400,
code: "github_issue",
description: "Error retrieving user info , please try again ",
});
}

const user: User & { avatar_url: string } = await userRes.json();

if (!Boolean(user.login)) {
return Response.json({
status: 400,
code: "github_issue",
description: "Invalid Github user data",
});
}

// save user to database

try {
await saveUser({
login: user.login,
email: user.email,
avatar: user.avatar_url,
bio: user.bio,
name: user.name,
});
// res.status(200).json(user);
// fetch images and dont wait for response
} catch (error) {
console.log(error);

return Response.json({
status: 400,
code: "error_database",
description: "Error saving to database ",
error,
});
}

redirect(`/ticket/${user.login}/me`);
}
13 changes: 11 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ import { SalLoader } from "@/components/sal-loader";

// import localFont from "@next/font/local";

import { Metadata } from "next";
import { Metadata, Viewport } from "next";

export const viewport: Viewport = {
themeColor: "#78543E",
width: "device-width",
initialScale: 1,
maximumScale: 1,
userScalable: false,
};

// either Static metadata
export const metadata: Metadata = {
Expand All @@ -17,10 +25,11 @@ export const metadata: Metadata = {
default: "سوق التيك المغربي | BlaBlaConf 2024",
},
description:
"BlaBlaConf 22 | 5+1 Days and 5+1 Tracks covering hottest Technology Trends in Darija",
"5+1 Days and 5+1 Tracks covering hottest Technology Trends in Darija",
alternates: {
canonical: "/",
},

openGraph: {
title: "سوق التيك المغربي | BlaBlaConf 2024",
description:
Expand Down
22 changes: 18 additions & 4 deletions app/ticket/[username]/me/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { TicketHero } from "@/components/ticket-hero";
import { getUserInfo } from "@/utils/ticket-service";
import type { Metadata } from "next";

export default async function Page({
params,
}: {
type Props = {
params: { username: string };
}) {
searchParams: { [key: string]: string | string[] | undefined };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
// read route params
const { metadata } = await getUserInfo(params.username);

// optionally access and extend (rather than replace) parent metadata
if (metadata) {
return metadata;
}

return {};
}

export default async function Page({ params }: Props) {
const { user } = await getUserInfo(params.username);
return <TicketHero {...user} />;
}
23 changes: 18 additions & 5 deletions app/ticket/[username]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { TicketHero } from "@/components/ticket-hero";
import { getUserInfo } from "@/utils/ticket-service";
import type { Metadata } from "next";

export default async function Page({
params,
}: {
type Props = {
params: { username: string };
}) {
// TODO fetch user da
searchParams: { [key: string]: string | string[] | undefined };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
// read route params
const { metadata } = await getUserInfo(params.username);

// optionally access and extend (rather than replace) parent metadata
if (metadata) {
return metadata;
}

return {};
}

export default async function Page({ params }: Props) {
const { user } = await getUserInfo(params.username);
return <TicketHero {...user} url={undefined} />;
}
152 changes: 152 additions & 0 deletions app/ticket/image/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { ImageResponse } from "next/og";
// App router includes @vercel/og.
// No need to install it.

export const runtime = "edge";

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);

// ?title=<title>
const hasName = searchParams.has("name");
const hasLogin = searchParams.has("login");
const hasAvatar = searchParams.has("avatar");
const hasTicketNumber = searchParams.has("ticketNumber");
const name = hasName ? searchParams.get("name") : "Name";
const login = hasLogin ? searchParams.get("login") : "";
const avatar = hasAvatar ? searchParams.get("avatar") : "";
const avatarUrl = avatar === null ? "" : avatar;
const ticketNumber = hasTicketNumber ? searchParams.get("ticketNumber") : "";
const number =
new Array(7 + 1 - (ticketNumber + "").length).join("0") + ticketNumber;
if (!login) {
return new ImageResponse(
(
<div
style={{
display: "flex",
fontSize: 60,
color: "black",
background: "#f6f6f6",
width: "100%",
height: "100%",
paddingTop: 50,
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
}}
>
Ticket dosn&apos;t exist please visit blablaconf.com
</div>
),
{
width: 1200,
height: 630,
}
);
}

return new ImageResponse(
(
<div
style={{
display: "flex",
fontSize: 60,
color: "black",
background: "#f6f6f6",
width: "100%",
height: "100%",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
<img
width="1200"
height="630"
src={`https://res.cloudinary.com/duko2tssr/image/upload/v1706825045/ticket-back_b6qvdk.jpg`}
/>
<div
style={{
display: "flex",
position: "absolute",
top: 70,
left: 0,
right: 82,
height: 150,
flexDirection: "row-reverse",
}}
>
<img
width="150"
height="150"
style={{
borderRadius: 100,
}}
src={avatarUrl}
/>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "flex-end",
justifyContent: "center",
marginRight: 30,
}}
>
<span
style={{
color: "white",
textAlign: "right",
fontWeight: "900",
fontSize: 37,
}}
>
{name}
</span>
<span
style={{
color: "white",
textAlign: "right",
fontSize: 30,
marginTop: 0,
}}
>
@{login}
</span>
</div>
</div>

<div
style={{
display: "flex",
position: "absolute",
top: 250,
right: 150,
height: 150,
width: 300,
// flexDirection: "row-reverse",
transform: "rotate(-40deg)",
}}
>
<span
style={{
color: "white",
opacity: 0.5,
textAlign: "right",
fontWeight: "900",
fontSize: 54,
}}
>
N {number}{" "}
</span>
</div>
</div>
),
{
width: 1200,
height: 630,
}
);
}
2 changes: 1 addition & 1 deletion components/github-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const GithubButton = () => {
<a
href={githubUrl}
onClick={() => setLoading(true)}
className="shrink-0 m-2 rounded-full bg-[#006233] px-8 py-3 font-medium text-white focus:bg-[#006233] focus:outline-none hover:bg-[#006233]"
className="shrink-0 mt-4 relative px-8 py-3 rounded-full border-2 bg-white/60 flex items-center justify-center font-medium hover:scale-105 transition-all "
>
{loading ? "Loading Github Profile ...." : "Customize with Github"}
</a>
Expand Down
2 changes: 1 addition & 1 deletion components/share-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const ShareActions = ({ shareUrl }: { shareUrl: string }) => {
return (
<div className="flex md:flex-row flex-col items-center mt-4">
<button
className="shrink-0 rounded-full bg-[#006233] px-8 py-3 font-medium text-white focus:bg-[#006233] focus:outline-none hover:bg-[#006233]"
className="shrink-0 relative px-8 py-2 rounded-full bg-white/60 font-medium hover:scale-105 transition-all "
onClick={() => {
setCopied(true);
copyToClipboard(shareUrl);
Expand Down
Loading

0 comments on commit 7c2e6d4

Please sign in to comment.