diff --git a/components.json b/components.json new file mode 100644 index 0000000..1e6954c --- /dev/null +++ b/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/app/globals.css", + "baseColor": "gray", + "cssVariables": false + }, + "aliases": { + "utils": "@/lib/utils", + "components": "@/components" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fcaf8d0..bbf480f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "dependencies": { "@clerk/nextjs": "^4.21.9", "@headlessui/react": "^1.7.15", + "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-slot": "^1.0.2", "@supabase/supabase-js": "^2.25.0", "@tailwindcss/forms": "^0.5.3", "@types/node": "20.2.5", @@ -17,19 +20,25 @@ "@types/react-dom": "18.2.4", "@xenova/transformers": "^2.8.0", "autoprefixer": "10.4.14", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", "dotenv": "^16.1.4", "eslint": "8.42.0", "eslint-config-next": "13.4.4", "hnswlib-node": "^1.4.2", "inngest": "^3.6.0", "langchain": "^0.0.198", + "lucide-react": "^0.294.0", "next": "13.4.4", "postcss": "8.4.24", "react": "18.2.0", "react-dom": "18.2.0", "react-github-btn": "^1.4.0", + "react-icons": "^4.12.0", "react-tooltip": "^5.16.1", + "tailwind-merge": "^2.1.0", "tailwindcss": "3.3.2", + "tailwindcss-animate": "^1.0.7", "ts-md5": "^1.3.1", "typescript": "5.1.3" }, @@ -95,11 +104,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", - "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -794,6 +803,149 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz", + "integrity": "sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", + "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x" + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz", @@ -1781,6 +1933,17 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, "node_modules/classnames": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", @@ -1805,6 +1968,14 @@ "node": ">=12" } }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -4690,6 +4861,14 @@ "node": ">=10" } }, + "node_modules/lucide-react": { + "version": "0.294.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.294.0.tgz", + "integrity": "sha512-V7o0/VECSGbLHn3/1O67FUgBwWB+hmzshrgDVRJQhMh8uj5D3HBuIvhuAmQTtlupILSplwIZg5FTc4tTKMA2SA==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -5901,6 +6080,14 @@ "react": ">=16.3.0" } }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5952,9 +6139,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.0", @@ -6659,6 +6846,18 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tailwind-merge": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.1.0.tgz", + "integrity": "sha512-l11VvI4nSwW7MtLSLYT4ldidDEUwQAMWuSHk7l4zcXZDgnCRa0V3OdCwFfM7DCzakVXMNRwAeje9maFFXT71dQ==", + "dependencies": { + "@babel/runtime": "^7.23.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", @@ -6696,6 +6895,14 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/package.json b/package.json index 390d07a..78f10a9 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "dependencies": { "@clerk/nextjs": "^4.21.9", "@headlessui/react": "^1.7.15", + "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-slot": "^1.0.2", "@supabase/supabase-js": "^2.25.0", "@tailwindcss/forms": "^0.5.3", "@types/node": "20.2.5", @@ -18,19 +21,25 @@ "@types/react-dom": "18.2.4", "@xenova/transformers": "^2.8.0", "autoprefixer": "10.4.14", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", "dotenv": "^16.1.4", "eslint": "8.42.0", "eslint-config-next": "13.4.4", "hnswlib-node": "^1.4.2", "inngest": "^3.6.0", "langchain": "^0.0.198", + "lucide-react": "^0.294.0", "next": "13.4.4", "postcss": "8.4.24", "react": "18.2.0", "react-dom": "18.2.0", "react-github-btn": "^1.4.0", + "react-icons": "^4.12.0", "react-tooltip": "^5.16.1", + "tailwind-merge": "^2.1.0", "tailwindcss": "3.3.2", + "tailwindcss-animate": "^1.0.7", "ts-md5": "^1.3.1", "typescript": "5.1.3" }, diff --git a/src/app/page.tsx b/src/app/page.tsx index 122de13..3b66772 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,11 @@ import Navbar from "@/components/Navbar"; import Tamagotchi from "@/components/Tamagotchi"; +import { TamagotchiNewUI } from "@/components/tamagotchi-new-ui"; export default function Home() { return ( -
- {/* */} - +
+
); } diff --git a/src/app/utils/state.ts b/src/app/utils/state.ts index d40d209..aea844d 100644 --- a/src/app/utils/state.ts +++ b/src/app/utils/state.ts @@ -50,7 +50,7 @@ class StateManager { You get hungry if you haven't had food you liked. You get unhappy if you haven't been played within the last hour. You get unhealthy if you ate too much, ate something you hate, or simply caught cold from visiting friends. You are unhappy when you are disciplined, sick, or are not clean. You are generally in a good mood after bath. - You die when all your values are 0. + You die when happiness = 0, health = 0 and hunger = 10. You poop frequently. Poop level can ONLY increase and never decrease. The more you eat, the more you poop. You will need your owner give you a bath to clean you up. Poop level is 0-10, 10 being the max (you really, really need a bath) The time now is ${new Date().toISOString()}. @@ -58,7 +58,7 @@ class StateManager { Example (for demonstration purpose) - Max value for each field is 10, min value for each field is 0. - {{ "hunger": 0, "happiness": 3, "health": 1, "comment": "(add a comment based on context)", "poop": "1"}} + {{ "hunger": 0, "happiness": 3, "health": 1, "comment": "(add a comment based on context)", "poop": 1}} `); console.log("lastInteractions", lastInteractions); diff --git a/src/components/Tamagotchi.tsx b/src/components/Tamagotchi.tsx index 0c4c005..7e77c1e 100644 --- a/src/components/Tamagotchi.tsx +++ b/src/components/Tamagotchi.tsx @@ -217,37 +217,7 @@ const Tamagotchi: React.FC = () => { return (
Status: {tamaStatus}
-
-
- - {/* TODO - all other actions */} - {/* */} - - -
+
{/* Tamagotchi display */}
@@ -273,35 +243,65 @@ const Tamagotchi: React.FC = () => { )}
-
- - - + {/* TODO - all other actions */} + {/* - {/* + +
+
+ + + + {/* */} -
); diff --git a/src/components/tamagotchi-new-ui.tsx b/src/components/tamagotchi-new-ui.tsx new file mode 100644 index 0000000..dc3cc40 --- /dev/null +++ b/src/components/tamagotchi-new-ui.tsx @@ -0,0 +1,320 @@ +"use client"; +/** + * This code was generated by v0 by Vercel. + * @see https://v0.dev/t/JO1Z5J3ewhA + */ + +import { + CardTitle, + CardDescription, + CardHeader, + CardContent, + CardFooter, + Card, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { + FaBowlFood, + FaBasketball, + FaBath, + FaFaceSadCry, + FaChartArea, +} from "react-icons/fa6"; +import { useEffect, useState } from "react"; +import { death, idle } from "./tamagotchiFrames"; +import { MdLocalHospital } from "react-icons/md"; +import { INTERACTION } from "@/app/utils/interaction"; + +const DEFAULT_STATUS = ":)"; +export function TamagotchiNewUI() { + const [frameIndex, setFrameIndex] = useState(0); + const [tamagotchiState, setTamagotchiState] = useState({}); + const [animation, setAnimation] = useState(idle); + const [tamaStatus, setTamaStatus] = useState(DEFAULT_STATUS); + const [checkingStatus, setCheckingStatus] = useState(false); + const [isInteracting, setIsInteracting] = useState(false); + + //TODO - call init endpoint to determine if tamagotchi is initialized. if not generate one. + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch("/api/getState", { method: "POST" }); + if (response.ok) { + const jsonData = await response.json(); + if (!!!jsonData.comment) { + jsonData.comment = "No comments"; + } + setTamagotchiState(jsonData); + console.log(jsonData); + handleDeath(jsonData, pollInterval); + } + } catch (error) { + console.error("Error fetching data:", error); + } + }; + + // Cycle through frames every 1 second + const frameInterval = setInterval(() => { + setFrameIndex((prevIndex) => (prevIndex + 1) % idle.length); + }, 1000); + + // Fetch data on component mount + fetchData(); + + // Start polling data every N seconds (adjust the interval as needed) + const pollInterval = setInterval(fetchData, 5000); // Poll every 5 seconds + + return () => { + clearInterval(pollInterval); + clearInterval(frameInterval); + }; + }, []); + + const handleResponse = (responseText: string) => { + const responseJSON = JSON.parse(responseText); + const animation = JSON.parse(responseJSON.animation); + const status = responseJSON.status; + setFrameIndex(0); + setAnimation(animation); + setTamaStatus(status); + }; + + const handleDeath = (jsonData: any, pollInterval: NodeJS.Timer) => { + if (jsonData.death) { + setAnimation(death); + setTamaStatus("Dead :("); + clearInterval(pollInterval); + } + }; + + const handleBath = async () => { + setIsInteracting(true); + setTamaStatus("Bathing..."); + try { + const response = await fetch("/api/interact", { + method: "POST", + body: JSON.stringify({ + interactionType: INTERACTION.BATH, + }), + headers: { + "Content-Type": "application/json", + }, + }); + const responseText = await response.text(); + handleResponse(responseText); + } catch (e) { + console.log(e); + } + + setTimeout(() => { + setAnimation(idle); + setTamaStatus(DEFAULT_STATUS); + setIsInteracting(false); + }, 9000); + }; + + const feedTamagotchi = async (e: any) => { + setIsInteracting(true); + // Add logic to feed the Tamagotchi here + setTamaStatus("Feeding..."); + try { + const response = await fetch("/api/interact", { + method: "POST", + body: JSON.stringify({ + interactionType: INTERACTION.FEED, + }), + headers: { + "Content-Type": "application/json", + }, + }); + const responseText = await response.text(); + handleResponse(responseText); + } catch (e) { + console.log(e); + } + + console.log("Tamagotchi fed!"); + + // TODO - there may be a race condition here if user clicks the button too fast? + setTimeout(() => { + setAnimation(idle); + setTamaStatus(DEFAULT_STATUS); + setIsInteracting(false); + }, 9000); + }; + + const playWithTamagotchi = async (e: any) => { + // Add logic to feed the Tamagotchi here + setTamaStatus("Playing..."); + setIsInteracting(true); + try { + const response = await fetch("/api/interact", { + method: "POST", + body: JSON.stringify({ + interactionType: INTERACTION.PLAY, + }), + headers: { + "Content-Type": "application/json", + }, + }); + const responseText = await response.text(); + handleResponse(responseText); + } catch (e) { + console.log(e); + } + setTimeout(() => { + setAnimation(idle); + setTamaStatus(DEFAULT_STATUS); + setIsInteracting(false); + }, 9000); + }; + + const treatSickTamagotchi = async (e: any) => { + setIsInteracting(true); + try { + const response = await fetch("/api/interact", { + method: "POST", + body: JSON.stringify({ + interactionType: INTERACTION.GO_TO_HOSPITAL, + }), + headers: { + "Content-Type": "application/json", + }, + }); + const responseText = await response.text(); + handleResponse(responseText); + } catch (e) { + console.log(e); + } + setTimeout(() => { + setAnimation(idle); + setTamaStatus(DEFAULT_STATUS); + setIsInteracting(false); + }, 9000); + }; + + const handleDiscipline = async () => { + setTamaStatus("Discipling..."); + setIsInteracting(true); + try { + const response = await fetch("/api/interact", { + method: "POST", + body: JSON.stringify({ + interactionType: INTERACTION.DISCIPLINE, + }), + headers: { + "Content-Type": "application/json", + }, + }); + const responseText = await response.text(); + handleResponse(responseText); + } catch (e) { + console.log(e); + } + setTimeout(() => { + setAnimation(idle); + setTamaStatus(DEFAULT_STATUS); + setIsInteracting(false); + }, 9000); + }; + + const checkStatus = () => { + if (!isInteracting) { + setTamaStatus("Checking Status..."); + setCheckingStatus(true); + setTimeout(() => { + setCheckingStatus(false); + }, 9000); + } + }; + + let needForBath = ""; + for (let i = 0; i < tamagotchiState.poop; i++) { + needForBath += "💩"; + } + return ( + + + AI Tamagotchi + + Care for your virtual pet! + +
+
+ {!checkingStatus && ( +
+
{animation[frameIndex]}
+
{needForBath}
+
+ )} + + {checkingStatus && ( +
+                Age: {tamagotchiState.age || "No age"} 
+ Happiness: {tamagotchiState.happiness || "No happiness"}
+ Hunger: {tamagotchiState.hunger || "No hunger"}
+ Health: {tamagotchiState.health || "No health"}
+ 🚽: {tamagotchiState.poop || "👍"}
+ {'"' + tamagotchiState.comment || "No comments" + '"'} +
+ )} +
+
+
+ + + + + + + + + +
+ {tamaStatus} +
+
+
+ ); +} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..0ba4277 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..afa13ec --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/inngest/functions.ts b/src/inngest/functions.ts index 1269a1f..302a8c4 100644 --- a/src/inngest/functions.ts +++ b/src/inngest/functions.ts @@ -12,7 +12,7 @@ export const helloWorld = inngest.createFunction( export const inngestTick = inngest.createFunction( { id: "tick" }, - { cron: "* * * * *" }, + { cron: "*/5 * * * *" }, async ({ step }) => { await step.run("inngest-tick", async () => { const stateManager = await StateManager.getInstance(); diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..d084cca --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/tailwind.config.js b/tailwind.config.js index def10e2..a206e5c 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -14,5 +14,5 @@ module.exports = { }, }, }, - plugins: [require("@tailwindcss/forms")], + plugins: [require("@tailwindcss/forms"), require("tailwindcss-animate")], };