Skip to content
Open
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
51 changes: 2 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,5 @@
# Starter Kit
# Wado Sanzo Colors

## Description

This is a starter kit for React with Next.js and Typescript.
It includes an optional SDK and Supabase database.

## Installation

1. Install bun `curl -fsSL https://bun.sh/install | bash`
2. Clone the repository `git clone https://github.com/seanwessmith/starter-kit`
3. In the repo root `bun install` to install the dependencies
4. In the repo root `bun start` to start the project

## Usage

### Client Pages

1. New pages go in `/app/[page name]/page.tsx`. Where page name is the name of the page and the route.
2. Update /app/clientComponent.tsx to navigate to the appropriate page.

### SDK Routes (optional)

1. New sdk routes go in `/src/server/routers/[route name]`. Where route name is the new route.
2. Create an `/src/server/routers/[route name]/index.ts` file that will mergeRouters from sibling files.
3. Create sibling files with the second route name `/src/server/routers/[route name]/[second route name].ts`.

### Supabase (optional)

1. In the repo root install Supabase cli `brew install supabase/tap/supabase`
2. In the repo root run `supabase login`
3. In the repo root run `bun supabase:create:demo`
4. Open the SQL editor on supabase.com. replace XXXXX with your project id ex: `https://supabase.com/dashboard/project/XXXXX/sql`
5. In the Supabase sql text area run:

```sql
CREATE TABLE people (
id bigint primary key generated always as identity,
name VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
startDateTimestamp TIMESTAMP NOT NULL
);
INSERT INTO people (name, title, startDateTimestamp) VALUES
('John Doe', 'Software Engineer', timestamp '2021-01-01 00:00:00.001'),
('Jane Doe', 'Product Manager', timestamp '2022-02-02 00:00:00.001'),
('Alice Doe', 'Designer', timestamp '2023-03-03 00:00:00.001'),
('Bob Doe', 'QA Engineer', timestamp '2024-04-04 00:00:00.001');
```

6. Back in the repo root run `bun run supabase:types`.
7. Uncomment `/app/about/page.tsx` supabase lines. comment out the SDK lines. Alternatively you can comment out the Supabase lines in the SDK if you'd prefer for supabase calls to come from the server.
8. [NOTE] Anytime the schema changes run `bun run supabase:types`.
A GUI for Wado Sanzo Colors
212 changes: 153 additions & 59 deletions app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,165 @@
"use client";
import React, { useEffect, useState } from "react";
import colors from "../assets/colors.json";

import { useEffect, useState } from "react";
// import { createClient } from "@supabase/supabase-js";
// import { Database } from "@/server/types/database.types";
import { sdk } from "@/server/sdkHandler";
import "./style.scss";
interface Color {
index: number;
name: string;
slug: string;
cmyk_array: number[];
cmyk: string;
rgb_array: number[];
rgb: string;
hex: string;
combinations: number[];
use_count: number;
}

// const supabase = createClient<Database>(
// process.env.NEXT_PUBLIC_SUPABASE_URL!,
// process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
// );
const ColorPalette = () => {
const [selectedColor, setSelectedColor] = useState<Color | null>(null);
const [copiedHexes, setCopiedHexes] = useState<
{ hex: string; timestamp: number }[]
>([]);

export default function AboutPage() {
const [people, setPeople] = useState<StarterKit.Person[]>([]);
const [error, setError] = useState<string | undefined>();
const [loading, setLoading] = useState(false);
useEffect(() => {
document.title = "Starter Kit - About";
}, []);
useEffect(() => {
async function fetchData() {
// const { data } = await supabase.from("people").select("*").order("name"); // Use Supabase on the client
const { people: data } = await sdk.people.employed.get.query(); // Use Supabase on the server
if (!data) {
setError("Failed to fetch data from Supabase.");
} else {
setPeople(data);
}
setLoading(true);
}
fetchData();
const timer = setInterval(() => {
setCopiedHexes((hexes) =>
hexes.filter((hex) => Date.now() - hex.timestamp < 1000)
);
}, 1000);
return () => clearInterval(timer);
}, []);

function timestampToDate(timestamp: string) {
return new Date(parseInt(timestamp) * 1000).toLocaleDateString();
const handleColorClick = (color: Color) => {
setSelectedColor(color);
navigator.clipboard.writeText(color?.hex || "");
setCopiedHexes((hexes) => [
...hexes,
{ hex: color.hex, timestamp: Date.now() },
]);
};
const handleSubColorClick = (hex?: string) => {
if (!hex) return;
setCopiedHexes((hexes) => [...hexes, { hex, timestamp: Date.now() }]);
};

// Converts the hex string to RGB values using parseInt and bitwise operations.
// Calculates the relative luminance of the color using the formula recommended by the W3C
// for determining color brightness. This formula takes into account the different
// sensitivity of the human eye to different wavelengths of light.
// luminance > 0.5 (i.e., a bright color) => return black else return white
function getTextColor(bgColor: string): string {
// Convert hex color to RGB values
const r = parseInt(bgColor.slice(1, 3), 16);
const g = parseInt(bgColor.slice(3, 5), 16);
const b = parseInt(bgColor.slice(5, 7), 16);

// Calculate the relative luminance
// (using the formula from W3C for color brightness)
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;

// Return black for bright colors, white for dark colors
return luminance > 0.5 ? "#000000" : "#FFFFFF";
}

const copiedDiv = (
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
backgroundColor: "rgba(0, 0, 0, 0.7)",
color: "#fff",
padding: "5px 10px",
borderRadius: "5px",
zIndex: 2,
}}
>
Copied!
</div>
);

const nameDiv = (color: Color) => (
<p
style={{
color: getTextColor(color.hex),
}}
className="text-xs absolute bottom-0 left-1"
>
{selectedColor?.hex === color.hex && selectedColor.name}
</p>
);

return (
<div className="about-page">
<h1>About Page</h1>
<p></p>
<div>
{loading ? <></> : <p>Loading...</p>}
{error ? <p>{error}</p> : <></>}
{people.length > 0 ? (
<table>
<thead>
<tr>
<th className="noselect">ID</th>
<th className="noselect">Name</th>
<th className="noselect">Title</th>
<th className="noselect">Start Date</th>
</tr>
</thead>
<tbody>
{people.map((person, index) => (
<tr key={index}>
<td>{person.id}</td>
<td>{person.name}</td>
<td>{person.title}</td>
<td>{timestampToDate(person.startdatetimestamp)}</td>
</tr>
))}
</tbody>
</table>
) : null}
<div className="flex max-w-vw">
<div className="w-2/3">
<h1 className="ml-3">A Dictionary of Color Combinations</h1>
<div style={{ display: "flex", flexWrap: "wrap" }}>
{colors.map((color) => (
<div
key={color.index}
onClick={() => handleColorClick(color)}
style={{
position: "relative",
backgroundColor: color.hex,
width: "100px",
height: "100px",
margin: "10px",
cursor: "pointer",
fontWeight: "bold",
color: getTextColor(color.hex),
border:
selectedColor && selectedColor.index === color.index
? "3px solid black"
: "3px solid transparent",
boxSizing: "border-box",
}}
>
{selectedColor?.hex === color.hex ? color.hex : ""}
{copiedHexes.map((cp) => cp.hex).includes(color.hex) && copiedDiv}
{selectedColor?.name === color.name && nameDiv(selectedColor)}
</div>
))}
</div>
</div>
<div className="-mt-3 w-1/3">
<h2>{selectedColor?.name}</h2>
<div className="flex -ml-3 flex-col overflow-hidden">
{selectedColor?.combinations.map((combIndex) => {
const combinationColors = colors.filter((c) =>
c.combinations.includes(combIndex)
);
console.log(combinationColors);
return (
<div className="mt-10 flex flex-row" key={combIndex}>
<p className="ml-3 w-12">{combIndex}</p>
<div className="flex flex-wrap flex-row gap-4">
{combinationColors.map((color) => (
<div
key={combIndex + color.index}
onClick={() => handleSubColorClick(color?.hex)}
className={`relative min-w-24 min-h-24 border-transparent border-4 hover:border-black cursor-pointer`}
style={{
backgroundColor: color?.hex,
boxSizing: "border-box",
color: color?.hex ? getTextColor(color.hex) : "",
}}
>
{color?.hex}
{copiedHexes
.map((cp) => cp.hex)
.includes(color?.hex || "") && copiedDiv}
{nameDiv(color)}
</div>
))}
</div>
</div>
);
})}
</div>
</div>
</div>
);
}
};

export default ColorPalette;
Loading