Skip to content
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
34 changes: 34 additions & 0 deletions client/src/components/ui/goBack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Link from "next/link";

const ButtonGallery = () => {
return (
<Link href="/artwork" aria-label="Go back to gallery">
<button
className="bg-neutral-1 text-light-3 group relative mb-10 h-14 w-48 rounded-2xl text-center text-xl font-semibold"
type="button"
>
<div className="bg-light-2 absolute left-1 top-[4px] z-10 flex h-12 w-1/4 items-center justify-center rounded-xl duration-500 group-hover:w-[184px]">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024 1024"
height="25px"
width="25px"
aria-hidden="true"
>
<path
d="M224 480h640a32 32 0 1 1 0 64H224a32 32 0 0 1 0-64z"
fill="#000000"
/>
<path
d="m237.248 512 265.408 265.344a32 32 0 0 1-45.312 45.312l-288-288a32 32 0 0 1 0-45.312l288-288a32 32 0 1 1 45.312 45.312L237.248 512z"
fill="#000000"
/>
</svg>
</div>
<p className="translate-x-2">Gallery</p>
</button>
</Link>
);
};

export default ButtonGallery;
30 changes: 30 additions & 0 deletions client/src/components/ui/imageFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Image from "next/image";
import React from "react";

interface CardProps {
imageSrc?: string;
imageAlt?: string;
children?: React.ReactNode;
}

const Card = ({ imageSrc, imageAlt = "Artwork", children }: CardProps) => {
return (
<div className="group p-4">
<div className="box-border flex h-[20rem] w-[20rem] flex-1 cursor-pointer select-none items-center justify-center self-stretch overflow-hidden rounded-[10px] border border-white bg-[#CED1FE] shadow-[12px_17px_51px_rgba(0,0,0,0.22)] backdrop-blur-md transition-all duration-500 hover:scale-105 hover:border-black active:rotate-[1.7deg] active:scale-95">
{imageSrc ? (
<Image
src={imageSrc}
alt={imageAlt}
width={190}
height={254}
className="h-full w-full object-cover"
/>
) : (
children || <span className="font-bold text-black">No Image</span>
)}
</div>
</div>
);
};

export default Card;
49 changes: 49 additions & 0 deletions client/src/hooks/useArtworkData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Art } from "@/types/art";

export const generateMockArtworks = (count: number): Art[] => {
const artworks: Art[] = [];
for (let i = 1; i <= count; i++) {
artworks.push({
id: i,
name: `Artwork ${i}`,
description: "Mock artwork description",
//source_game: "Mock Game",
path_to_media: "",
active: true,
contributors: [],
//created_at: new Date().toISOString(),
});
}
return artworks;
};

export const generateMockArtwork = (id: string): Art => {
return {
id: Number(id),
name: "Mock Artwork Title",
description:
"Lorem ipsum dolor sit amet. Non numquam dicta nam autem dicta 33 error molestias et repellat consequatur eum iste expedita est dolorem libero et quas provident!",
//source_game: "Mock Game",
path_to_media: "",
active: true,
//created_at: new Date().toISOString(),
contributors: [
{
id: 1,
art_id: Number(id),
member_name: "Contributor 1",
role: "user1",
discord_url: "https://discord.com",
instagram_url: "",
},
{
id: 2,
art_id: Number(id),
member_name: "Contributor 2",
role: "user2",
discord_url: "",
instagram_url: "https://instagram.com",
},
],
};
};
56 changes: 43 additions & 13 deletions client/src/pages/artwork/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { GetServerSideProps } from "next";
import Image from "next/image";
import Link from "next/link";
import { JSX } from "react";

import ButtonGallery from "@/components/ui/goBack";
import { generateMockArtwork } from "@/hooks/useArtworkData";
import api from "@/lib/api";
import { Art } from "@/types/art";

Expand Down Expand Up @@ -102,27 +103,50 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) {
</div>
<div
data-layer="Frame 1100"
className="Frame1100 inline-flex flex-col items-start justify-center gap-10 bg-slate-950 p-3 md:pl-12"
className="Frame1100 mb-4 inline-flex flex-col items-start justify-center gap-10 bg-slate-950 p-3 md:pl-12"
>
<div
data-layer="< Gallery"
className="Gallery text-light-1 h-10 justify-start font-['DM_Sans'] text-3xl font-bold leading-10 tracking-tight"
>
<Link href="/artwork">&lt; Gallery</Link>
<ButtonGallery />
</div>
</div>
<div
data-layer="Frame 1099"
className="Frame1099 bg-neutral-1 justify-start md:flex"
>
<div className="relative flex content-center justify-center">
<Image
src={artwork.path_to_media}
alt="Artwork image"
width={500}
height={500}
className="relative block sm:h-auto sm:max-w-full md:max-h-full"
/>
{artwork.path_to_media ? (
<Image
src={artwork.path_to_media}
alt="Artwork image"
width={500}
height={500}
className="relative block sm:h-auto sm:max-w-full md:max-h-full"
/>
) : (
// in case fail to load image or no image in db yet
<div
data-layer="Placeholder image"
className="PlaceholderImage bg-light-2 flex h-[500px] w-[500px] items-center justify-center rounded-[10px]"
>
<div data-svg-wrapper data-layer="Vector" className="Vector">
<svg
width="96"
height="96"
viewBox="0 0 96 96"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M96 85.3333V10.6667C96 4.8 91.2 0 85.3333 0H10.6667C4.8 0 0 4.8 0 10.6667V85.3333C0 91.2 4.8 96 10.6667 96H85.3333C91.2 96 96 91.2 96 85.3333ZM29.3333 56L42.6667 72.0533L61.3333 48L85.3333 80H10.6667L29.3333 56Z"
fill="var(--neutral-1, #1B1F4C)"
/>
</svg>
</div>
</div>
)}
</div>
<div
data-layer="Frame 1162"
Expand Down Expand Up @@ -201,7 +225,13 @@ export const getServerSideProps: GetServerSideProps<ArtworkPageProps> = async (
context,
) => {
const { id } = context.params as { id: string };
const artResponse = await api.get<Art>(`game-dev/arts/${id}`);
const artwork = artResponse.data;
return { props: { artwork } };
try {
const artResponse = await api.get<Art>(`game-dev/arts/${id}`);
const artwork = artResponse.data;
return { props: { artwork } };
} catch {
// Return mock data when API fails or DB is empty
const mockArtwork = generateMockArtwork(id);
return { props: { artwork: mockArtwork } };
}
};
76 changes: 46 additions & 30 deletions client/src/pages/artwork/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { GetServerSideProps } from "next";
import Image from "next/image";
import Link from "next/link";

import { Button } from "@/components/ui/button";
import Card from "@/components/ui/imageFrame";
import { generateMockArtworks } from "@/hooks/useArtworkData";
import api from "@/lib/api";
import { Art } from "@/types/art";
import { PageResult } from "@/types/page-response";

interface ArtworksPageProps {
pages: PageResult<Art>;
artworks: PageResult<Art>;
}

const PLACEHOLDER_ICON = (
Expand All @@ -31,29 +33,22 @@ function renderArtworkCard(artwork: Art) {
return (
<Link
href={`/artwork/${artwork.id}`}
key={artwork.id}
data-layer="Frame 1120"
className="Frame1120"
title={artwork.name}
>
<div
data-layer="Placeholder image"
className="PlaceholderImage border-light-2 flex h-[20rem] w-[25rem] flex-1 items-center justify-center self-stretch rounded-[10px] border-2 border-solid p-1"
<Card
imageSrc={artwork.path_to_media || undefined}
imageAlt={artwork.name}
>
<div className="relative h-full w-full">
<Image
src={artwork.path_to_media}
alt="Artwork image"
fill
objectFit="contain"
className="relative"
/>
</div>
</div>
{!artwork.path_to_media && PLACEHOLDER_ICON}
</Card>
</Link>
);
}

export default function ArtworksPage({ pages }: ArtworksPageProps) {
export default function ArtworksPage({ artworks }: ArtworksPageProps) {
return (
<div data-layer="Art Page General" className="ArtPageGeneral">
<div
Expand Down Expand Up @@ -84,23 +79,19 @@ export default function ArtworksPage({ pages }: ArtworksPageProps) {
data-layer="Auto Layout Horizontal"
className="AutoLayoutHorizontal items-start justify-start gap-6"
>
<div
data-layer="Button/Style2"
className="ButtonStyle2 size- bg-neutral-1 outline-neutral-1 flex items-center justify-center gap-2.5 overflow-hidden rounded-[10px] px-5 py-3 outline outline-1 outline-offset-[-1px]"
<Button
variant="outline"
size="lg"
className="rounded-[10px] font-['Jersey_10'] text-xl font-normal leading-6 tracking-wide"
>
<div
data-layer="Filled Button"
className="FilledButton text-light-1 justify-start font-['Jersey_10'] text-xl font-normal leading-6 tracking-wide"
>
More about us →
</div>
</div>
More about us →
</Button>
</div>
</div>

<div data-layer="Frame 1159" className="Frame1159 relative self-stretch">
<div className="Frame1098 aligne flex flex-row flex-wrap items-center justify-center gap-x-3 gap-y-20 bg-slate-950 py-14 pl-28 pr-24">
{pages.results.map((artwork) => renderArtworkCard(artwork))}
{artworks.results.map((artwork) => renderArtworkCard(artwork))}
</div>
</div>
<div
Expand All @@ -113,10 +104,35 @@ export default function ArtworksPage({ pages }: ArtworksPageProps) {
);
}

// ...existing code...

export const getServerSideProps: GetServerSideProps<
ArtworksPageProps
> = async () => {
const res = await api.get<PageResult<Art>>("game-dev/arts");
const pages = res.data;
return { props: { pages } };
try {
const res = await api.get<PageResult<Art>>("game-dev/arts");
return { props: { artworks: res.data } };
} catch {
// return {
// props: {
// artworks: {
// count: 0,
// next: null as unknown as string,
// previous: null as unknown as string,
// results: [] as Art[],
// },
// },
// }; ==> use when successfully populate db
const mockArtworks = generateMockArtworks(12);
return {
props: {
artworks: {
count: mockArtworks.length,
next: "",
previous: "",
results: mockArtworks,
},
},
};
}
};
12 changes: 6 additions & 6 deletions client/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@

--radius: 0.5rem;

--dark-2: #090A19;
--neutral-1: #1B1F4C;
--light-1: #FFFFFF;
--light-2: #CED1FE;
--light-3: #9CA4FD;
--dark-2: #090a19;
--neutral-1: #1b1f4c;
--light-1: #ffffff;
--light-2: #ced1fe;
--light-3: #9ca4fd;
}
}

Expand Down Expand Up @@ -68,4 +68,4 @@
}
.border-light-2 {
border-color: var(--light-2);
}
}
2 changes: 1 addition & 1 deletion server/api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,4 @@
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100,
}
}
4 changes: 2 additions & 2 deletions server/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
urlpatterns = [
path("admin/", admin.site.urls),
path("api/healthcheck/", include("api.healthcheck.urls")),
path("api/game-dev/", include("game_dev.urls")),
]
path("api/game-dev/", include("game_dev.urls")),
]
21 changes: 3 additions & 18 deletions server/game_dev/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,8 @@
from .models import Member, Art, ArtContributor


@admin.register(Member)
class MemberAdmin(admin.ModelAdmin):
list_display = ['name', 'active', 'pronouns']
list_filter = ['active']
search_fields = ['name']
admin.site.register(Member)

admin.site.register(Art)

@admin.register(Art)
class ArtAdmin(admin.ModelAdmin):
list_display = ['name', 'active']
list_filter = ['active']
search_fields = ['name']


@admin.register(ArtContributor)
class ArtContributorAdmin(admin.ModelAdmin):
list_display = ['art', 'member', 'role']
list_filter = ['role']
search_fields = ['member__name', 'art__name']
autocomplete_fields = ['art', 'member']
admin.site.register(ArtContributor)
Loading