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

feat: 3D badge #2

Merged
merged 4 commits into from
Aug 5, 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"ui:add": "bunx --bun shadcn-ui@latest add"
"ui:add": "bun x --bun shadcn-ui@latest add"
},
"dependencies": {
"@hookform/resolvers": "^3.3.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ useTexture.preload(
"https://github.com/user-attachments/assets/999b5d58-ac8a-4c20-8fc6-74e8ab7876e7",
);

export default function App() {
export default function Badge3D({ attendee }) {
const { debug } = useControls({ debug: false });
return (
<Canvas camera={{ position: [0, 0, 13], fov: 25 }}>
Expand All @@ -42,7 +42,7 @@ export default function App() {
gravity={[0, -40, 0]}
timeStep={1 / 60}
>
<Band />
<Band attendee={attendee} />
</Physics>
<Environment background blur={0.75}>
<color attach="background" args={["black"]} />
Expand Down Expand Up @@ -79,7 +79,7 @@ export default function App() {
);
}

function BadgeTexture() {
function BadgeTexture({ attendee }) {
// const viewport = useThree((state) => state.viewport);

return (
Expand All @@ -105,19 +105,31 @@ function BadgeTexture() {
bevelSize={0}
font="/Geist_Regular.json"
height={0}
position={[0.9, 2, 0]}
position={[1.05, 2.1, 0]}
rotation={[0, Math.PI, Math.PI]}
size={0.2}
letterSpacing={-0.03}
size={0.15}
letterSpacing={-0.02}
>
hyamero
{attendee.firstName}
</Text3D>
<Text3D
bevelEnabled={false}
bevelSize={0}
font="/Geist_Regular.json"
height={0}
position={[1.13, 3.1, 0]}
position={[1.23, 2.3, 0]}
rotation={[0, Math.PI, Math.PI]}
size={0.15}
letterSpacing={-0.02}
>
{attendee.lastName}
</Text3D>
<Text3D
bevelEnabled={false}
bevelSize={0}
font="/Geist_Regular.json"
height={0}
position={[1.3, 3.25, 0]}
rotation={[0, Math.PI, Math.PI]}
size={0.07}
letterSpacing={-0.008}
Expand All @@ -130,7 +142,7 @@ function BadgeTexture() {
);
}

function Band({ maxSpeed = 50, minSpeed = 10 }) {
function Band({ maxSpeed = 50, minSpeed = 10, attendee }) {
const band = useRef(), fixed = useRef(), j1 = useRef(), j2 = useRef(), j3 = useRef(), card = useRef() // prettier-ignore
const vec = new THREE.Vector3(), ang = new THREE.Vector3(), rot = new THREE.Vector3(), dir = new THREE.Vector3() // prettier-ignore
const segmentProps = {
Expand Down Expand Up @@ -280,8 +292,7 @@ function Band({ maxSpeed = 50, minSpeed = 10 }) {
roughness={0.3}
>
<RenderTexture attach="map" height={2000} width={2000}>
{/* <BadgeTexture user={user} /> */}
<BadgeTexture />
<BadgeTexture attendee={attendee} />
</RenderTexture>
</meshPhysicalMaterial>
</mesh>
Expand Down
48 changes: 48 additions & 0 deletions src/app/event/[name]/badge/components/badge-showcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import useMediaQuery from "@/hooks/use-media-query";
import Badge3D from "./badge-3d";
import { db } from "@/config/firebase";
import { doc } from "firebase/firestore";
import type { Attendee } from "@/lib/types";
import { usePathname, useSearchParams } from "next/navigation";
import { useDocumentOnce } from "react-firebase-hooks/firestore";
import Loading from "../../loading";

export default function BadgeShowcase() {
const isDesktop = useMediaQuery("(min-width: 768px)");

const pathname = usePathname();
const searchParams = useSearchParams();
const certId = searchParams.get("id");

const [attendeeValue, attendeeLoading] = useDocumentOnce(
doc(db, `${pathname.split("/")[2]}/data/certificates/${certId}`),
);

const attendee = {
...attendeeValue?.data(),
id: attendeeValue?.id,
} as Attendee;

if (attendeeLoading) return <Loading />;

if (!attendee.email)
return (
<div className="pt-40 text-center">
<h3 className="text-6xl font-bold capitalize">Certificate not found</h3>
</div>
);

return (
<>
{isDesktop ? (
<Badge3D attendee={attendee} />
) : (
<div>
<h1 className="mt-40 text-center text-2xl font-semibold">Badge 2D</h1>
</div>
)}
</>
);
}
13 changes: 13 additions & 0 deletions src/app/event/[name]/badge/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { doc, getDoc } from "firebase/firestore";
import BadgeShowcase from "./components/badge-showcase";
import { db } from "@/config/firebase";
import EventNotFound from "../../components/event-not-found";

export default async function Badge({ params }: { params: { name: string } }) {
const docRef = doc(db, params.name, "data");
const docSnap = await getDoc(docRef);

if (!docSnap.exists()) return <EventNotFound />;

return <BadgeShowcase />;
}
55 changes: 47 additions & 8 deletions src/app/event/components/event-landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ import {
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

export type EventDetails = {
title: string;
Expand All @@ -40,6 +48,8 @@ const formSchema = z.object({
message: "This field has to be filled",
})
.email("This is not a valid email"),

type: z.enum(["badge", "cert"]),
});

export default function EventLanding({
Expand All @@ -53,6 +63,7 @@ export default function EventLanding({
resolver: zodResolver(formSchema),
defaultValues: {
email: "dale@ban.com",
type: "badge",
},
});

Expand All @@ -70,7 +81,7 @@ export default function EventLanding({
let message = "⚠️ Certificate Not Found";
querySnapshot.forEach((doc) => {
if (doc.data().email) {
push(`/event/${eventCode}/cert?id=${doc.id}`);
push(`/event/${eventCode}/${values.type}?id=${doc.id}`);
message = "✅ Certificate found!";
return;
}
Expand Down Expand Up @@ -119,13 +130,41 @@ export default function EventLanding({
</FormItem>
)}
/>
<Button
type="submit"
className="w-full font-semibold"
disabled={loading}
>
{loading ? <Icons.spinner /> : "Get Certificate"}
</Button>
<div className="space-y-1">
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Select type</FormLabel>
<Select onValueChange={field.onChange} {...field}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem className="cursor-pointer" value="badge">
Badge
</SelectItem>
<SelectItem className="cursor-pointer" value="cert">
Certificate
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<Button
variant="secondary"
type="submit"
className="w-full font-medium"
disabled={loading}
>
{loading ? <Icons.spinner /> : "Generate"}
</Button>
</div>
</form>
</Form>
</CardContent>
Expand Down
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default async function Home() {
)}
>
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 transition ease-out hover:text-neutral-600 hover:duration-300 hover:dark:text-neutral-400">
<Link href="/badge">
<Link href={`/event/hacking-ai/badge?id=Z5jdHi6BmyT`}>
✨ Experimental:
<span className="font-semibold"> Visit 3D Badge</span>
</Link>
Expand Down
2 changes: 1 addition & 1 deletion src/components/event-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function EventButtons() {

const SearchEventForm = ({}) => {
const router = useRouter();
const [event, setEvent] = useState("");
const [event, setEvent] = useState("hacking-ai");
const [loading, setLoading] = useState(false);

const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/magicui/retro-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default function RetroGrid({ className }: { className?: string }) {
return (
<div
className={cn(
"pointer-events-none absolute size-full overflow-hidden opacity-70 [perspective:200px]",
"pointer-events-none absolute size-full overflow-hidden [perspective:200px]",
className,
)}
>
Expand Down