# Project export: Shamefully

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: The social network for accountability the social way
- Devpost: https://devpost.com/software/shamefully-e1l5qf
- GitHub: https://github.com/Dario-Soatto/shamefully
- Demo: http://shameful.ly/
- Team: 1 GitHub contributor(s) — Obinna Akahara (14 commits)

## Devpost submission (written by the team)

### Inspiration

Nearly everyone struggles with getting their goals accomplished: we set aspirations that will propel us forward but almost inevitably fall short—we lose motivation, habits die, and our goals die with them. Some existing applications help users accomplish their goals by creating stakes for if they fail, but no existing product leverages the most powerful motivator: shame. We hope to help people help themselves and get sh*t done through shaming mechanisms.

### What it does

Tell Shamefully what you want to do, when you want to do it, and who will keep you accountable. If you fail to upload evidence that you've done what you set out to do, Shamefully will call your friends, tell them you've failed, and encourage them to shame you. In addition, we've built an AI coach that helps motivate you to achieve your goals when you hit setbacks.

### How we built it

We split up into 2 teams and worked on the website and mobile app. The website was build with NextJS, and AWS Amplify and a couple UI libraries. Our goal was to build something pretty, and fast. The mobile app was built using flutter and firebase. The goal here was to get a prototype out as soon as possible so that we could test it. For our AI stack, we used a combination of GPT Vision and a new Voice AI service called Bland.ai that allowed us to make phone calls and have audio conversations with LLMs.

### Challenges we ran into

We had a myriad of challenges, from our initial prototype not working, to one of our teammates needing to go to urgent care. On the technical side, we mainly ran into issues with the voice API and text messaging. We found out that you need 3+ business days to get a legally registered phone number to send texts with. That led us to change our plans and modify the product (which led us to the Voice API) Halfway through the hackathon, one of our teammates found himself in need of urgent care. We had to drive around Palo Alto looking for any urgent care facility that was open and accepting patients. It took a 30 min drive to find one! We had a bunch more challenges, but it was all part of the fun.

### Accomplishments we're proud of

We're all strangers on the team. We all literally met on Friday, yet somehow came together to pull off something magical. The grit and determination that I saw reminded me of why I love hacking. We also built an entire mobile app, and entire website and API in 24 hours! And the best part is that product is fun to use (we're a tad bit biased)

### What we learned

We learned not to sleep at 6:00 AM

### What's next

Talk to users, and see if they want it. It would be nice to keep working on this and build it into a sustainable business that genuinely helps people.

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 68 recognized source files, 776 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (99 of 99)

```
.eslintignore
.eslintrc.json
.gitignore
.graphqlconfig.yml
.vscode/settings.json
amplify/.config/project-config.json
amplify/backend/analytics/shamefully/parameters.json
amplify/backend/analytics/shamefully/pinpoint-cloudformation-template.json
amplify/backend/api/shamefully/cli-inputs.json
amplify/backend/api/shamefully/parameters.json
amplify/backend/api/shamefully/resolvers/README.md
amplify/backend/api/shamefully/schema.graphql
amplify/backend/api/shamefully/stacks/CustomResources.json
amplify/backend/api/shamefully/transform.conf.json
amplify/backend/auth/shamefully9197354c/cli-inputs.json
amplify/backend/backend-config.json
amplify/backend/function/shamefullyNewUser/amplify.state
amplify/backend/function/shamefullyNewUser/custom-policies.json
amplify/backend/function/shamefullyNewUser/function-parameters.json
amplify/backend/function/shamefullyNewUser/shamefullyNewUser-cloudformation-template.json
amplify/backend/function/shamefullyNewUser/src/event.json
amplify/backend/function/shamefullyNewUser/src/index.js
amplify/backend/function/shamefullyNewUser/src/package.json
amplify/backend/function/shamefullyNewUser/src/ui-components/AICoachCreateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/AICoachCreateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/AICoachUpdateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/AICoachUpdateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/CheckInCreateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/CheckInCreateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/CheckInUpdateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/CheckInUpdateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/GoalCreateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/GoalCreateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/GoalUpdateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/GoalUpdateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/index.js
amplify/backend/function/shamefullyNewUser/src/ui-components/ShamerCreateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/ShamerCreateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/ShamerUpdateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/ShamerUpdateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/studioTheme.js
amplify/backend/function/shamefullyNewUser/src/ui-components/studioTheme.js.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/UserCreateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/UserCreateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/UserUpdateForm.d.ts
amplify/backend/function/shamefullyNewUser/src/ui-components/UserUpdateForm.jsx
amplify/backend/function/shamefullyNewUser/src/ui-components/utils.js
amplify/backend/storage/shamefully/cli-inputs.json
amplify/backend/tags.json
amplify/backend/types/amplify-dependent-resources-ref.d.ts
amplify/cli.json
amplify/package.json
amplify/team-provider-info.json
amplify/tsconfig.json
components.json
next.config.mjs
package.json
postcss.config.js
README.md
src/API.ts
src/app/api/call-me/route.ts
src/app/api/send-text/route.ts
src/app/globals.css
src/app/goals/page.tsx
src/app/layout.tsx
src/app/page.tsx
src/aws-exports.js
src/components/AppWrapper.tsx
src/graphql/mutations.ts
src/graphql/queries.ts
src/graphql/schema.json
src/graphql/subscriptions.ts
src/lib/utils.ts
src/ui-components/AICoachCreateForm.d.ts
src/ui-components/AICoachCreateForm.jsx
src/ui-components/AICoachUpdateForm.d.ts
src/ui-components/AICoachUpdateForm.jsx
src/ui-components/CheckInCreateForm.d.ts
src/ui-components/CheckInCreateForm.jsx
src/ui-components/CheckInUpdateForm.d.ts
src/ui-components/CheckInUpdateForm.jsx
src/ui-components/GoalCreateForm.d.ts
src/ui-components/GoalCreateForm.jsx
src/ui-components/GoalUpdateForm.d.ts
src/ui-components/GoalUpdateForm.jsx
src/ui-components/index.js
src/ui-components/ShamerCreateForm.d.ts
src/ui-components/ShamerCreateForm.jsx
src/ui-components/ShamerUpdateForm.d.ts
src/ui-components/ShamerUpdateForm.jsx
src/ui-components/studioTheme.js
src/ui-components/studioTheme.js.d.ts
src/ui-components/UserCreateForm.d.ts
src/ui-components/UserCreateForm.jsx
src/ui-components/UserUpdateForm.d.ts
src/ui-components/UserUpdateForm.jsx
src/ui-components/utils.js
tailwind.config.ts
tsconfig.json
```

### Dependencies

- amplify/backend/function/shamefullyNewUser/src/package.json: @types/aws-lambda@^8.10.92
- amplify/package.json: aws-sdk@^2.1560.0
- package.json: @aws-amplify/backend@^0.12.1, @aws-amplify/backend-cli@^0.11.1, @aws-amplify/ui-react@^6.1.4, @aws-sdk/client-sns@^3.516.0, @emotion/react@^11.11.3, @emotion/styled@^11.11.0, @mui/material@^5.15.10, @mui/x-date-pickers@^6.19.4, @nextui-org/react@^2.2.9, @radix-ui/react-icons@^1.3.0, @radix-ui/react-navigation-menu@^1.1.4, @splinetool/react-spline@^2.2.6, @types/node@^20, @types/react@^18, @types/react-dom@^18, autoprefixer@^10.0.1, aws-amplify@^6.0.16, aws-cdk@^2.128.0, aws-cdk-lib@^2.128.0, class-variance-authority@^0.7.0, clsx@^2.1.0, constructs@^10.3.0, dayjs@^1.11.10, eslint@^8, eslint-config-next@14.1.0, framer-motion@^11.0.5, ldrs@^1.0.1, next@14.1.0, postcss@^8, react@^18.2.0, react-dom@^18, react-icons@^5.0.1, react-toastify@^10.0.4, tailwind-merge@^2.2.1, tailwindcss@^3.3.0, tailwindcss-animate@^1.0.7, typescript@^5.3.3

### Recent commits (newest first)

- Stuff
- aws-exports
- Build fixes
- dayjs
- Looking pretty G
- Clean up again
- Clean up
- Spline
- Let's get shaming
- Goal creation
- Update
- Basic form
- Init
- Initial commit from Create Next App

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "shameful.ly",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@aws-amplify/ui-react": "^6.1.4",
    "@aws-sdk/client-sns": "^3.516.0",
    "@emotion/react": "^11.11.3",
    "@emotion/styled": "^11.11.0",
    "@mui/material": "^5.15.10",
    "@mui/x-date-pickers": "^6.19.4",
    "@nextui-org/react": "^2.2.9",
    "@radix-ui/react-icons": "^1.3.0",
    "@radix-ui/react-navigation-menu": "^1.1.4",
    "@splinetool/react-spline": "^2.2.6",
    "aws-amplify": "^6.0.16",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.0",
    "dayjs": "^1.11.10",
    "framer-motion": "^11.0.5",
    "ldrs": "^1.0.1",
    "next": "14.1.0",
    "react": "^18.2.0",
    "react-dom": "^18",
    "react-icons": "^5.0.1",
    "react-toastify": "^10.0.4",
    "tailwind-merge": "^2.2.1",
    "tailwindcss-animate": "^1.0.7"
  },
  "devDependencies": {
    "@aws-amplify/backend": "^0.12.1",
    "@aws-amplify/backend-cli": "^0.11.1",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "aws-cdk": "^2.128.0",
    "aws-cdk-lib": "^2.128.0",
    "constructs": "^10.3.0",
    "eslint": "^8",
    "eslint-config-next": "14.1.0",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5.3.3"
  }
}

```

### amplify/package.json

```
{
  "type": "module",
  "dependencies": {
    "aws-sdk": "^2.1560.0"
  }
}

```

### amplify/backend/function/shamefullyNewUser/src/package.json

```
{
  "name": "shamefullynewuser",
  "version": "2.0.0",
  "description": "Lambda function generated by Amplify",
  "main": "index.js",
  "license": "Apache-2.0",
  "devDependencies": {
    "@types/aws-lambda": "^8.10.92"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { twMerge } from "tailwind-merge";
import { AppWrapper } from "@/components/AppWrapper";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
	title: "Create Next App",
	description: "Generated by create next app",
};

export default function RootLayout({
	children,
}: Readonly<{
	children: React.ReactNode;
}>) {
	return (
		<html lang="en">
			<body className={twMerge("h-full w-full	", inter.className)}>
				<AppWrapper>{children}</AppWrapper>
			</body>
		</html>
	);
}

```

### src/ui-components/index.js

```javascript
/***************************************************************************
 * The contents of this file were generated with Amplify Studio.           *
 * Please refrain from making any modifications to this file.              *
 * Any changes to this file will be overwritten when running amplify pull. *
 **************************************************************************/

export { default as AICoachCreateForm } from "./AICoachCreateForm";
export { default as AICoachUpdateForm } from "./AICoachUpdateForm";
export { default as CheckInCreateForm } from "./CheckInCreateForm";
export { default as CheckInUpdateForm } from "./CheckInUpdateForm";
export { default as GoalCreateForm } from "./GoalCreateForm";
export { default as GoalUpdateForm } from "./GoalUpdateForm";
export { default as ShamerCreateForm } from "./ShamerCreateForm";
export { default as ShamerUpdateForm } from "./ShamerUpdateForm";
export { default as UserCreateForm } from "./UserCreateForm";
export { default as UserUpdateForm } from "./UserUpdateForm";
export { default as studioTheme } from "./studioTheme";

```

### src/app/page.tsx

```typescript
"use client";

import React, { type FC, useState, useEffect, HTMLAttributes } from "react";
import { Button } from "@nextui-org/react";
import { generateClient } from "aws-amplify/api";

import { Goal, User } from "@/API";
import { motion, AnimatePresence } from "framer-motion";
import { BsX, BsCheck } from "react-icons/bs";

import { getUser } from "@/graphql/queries";
import { CheckIn } from "../API";
import { updateCheckIn } from "@/graphql/mutations";
import { twMerge } from "tailwind-merge";
import Spline from "@splinetool/react-spline";

const client = generateClient({});

type HomePageProps = {
	params?: {
		id: string;
	};
	searchParams?: {
		search?: string;
	};
};

const HomePage: FC<HomePageProps> = () => {
	const [user, setUser] = useState<User>({} as User);
	const [splineLoaded, setSplineLoaded] = useState(false);

	useEffect(() => {
		async function getLoader() {
			const { spiral } = await import("ldrs");
			spiral.register();
		}
		getLoader();
	}, []);

	useEffect(() => {
		// Fetch the user
		client
			.graphql({ query: getUser, variables: { id: "demo" } })
			.then((response) => {
				console.log(response);

				if (!response.data.getUser) {
					console.error("No user found");
					return;
				}

				setUser(response.data.getUser as User);
			});
	}, []);

	const currentCheckIns: any = [];

	user.checkIns?.items?.forEach((checkIn) => {
		if (!checkIn) return;

		const isDeadlineSoon = (deadline: string) => {
			const deadlineDate = new Date(deadline).getTime();
			const now = new Date().getTime();
			const oneDayInMs = 24 * 60 * 60 * 1000;
			return deadlineDate - now < oneDayInMs && deadlineDate - now > 0;
		};

		if (!isDeadlineSoon(checkIn.deadline as string)) return;

		if (checkIn.status === "SUCCESSFUL" || checkIn.status === "FAILED") return;

		currentCheckIns.append(
			<CheckInComponent key={checkIn.id} checkIn={checkIn} />
		);
	});

	console.log("length", currentCheckIns?.length);

	return (
		<div className="w-full h-full flex flex-col justify-start py-20 pb-40 items-center gap-20">
			{user.id ? (
				<motion.div
					initial={{ opacity: 0 }}
					animate={{ opacity: 1 }}
					exit={{ opacity: 0 }}
					transition={{ duration: 0.5 }}
					className="w-full h-full justify-start items-center flex flex-col gap-10"
				>
					<div className="font-bold text-xl text-opacity-90">
						{`Hey ${user.firstName} ${user.lastName}`}
					</div>
					<div className="flex flex-col gap-4 w-5/6">
						{currentCheckIns?.length === 0 ? (
							<div className="w-full h-full flex flex-col justify-center items-center gap-4">
								{!splineLoaded && <l-spiral size={45} color="coral"></l-spiral>}
								<Spline
									// style={{
									// 	background:
									// 		"radial-gradient(circle, rgba(0,0,0,0.2	) 0%, rgba(0,0,0,0.1) 10%, rgba(0,0,0,0) 100%)",
									// }}
									className={twMerge(
										"!h-[50vh] opacity-0 transition-all  duration-300 -mt-40",
										splineLoaded && "opacity-100 -mt-0 "
									)}
									onLoad={() => {
										console.log("Spline loaded");
										setSplineLoaded(true);
									}}
									scene="https://prod.spline.design/DUCYHHZaaS0eoNm1/scene.splinecode"
								/>
								Nothing to see here, create some goals to get started!
								<Button color="primary">
									<a href="/goals">Get Started</a>
								</Button>
							</div>
						) : (
							<AnimatePresence presenceAffectsLayout>
								{currentCheckIns}
							</AnimatePresence>
						)}
					</div>

					{/* <div className="flex flex-col gap-4 w-5/6">
						{user.goals?.items?.map((goal) => {
							if (!goal) return null;
							console.log(goal);

							return <GoalComponent key={goal.id} goal={goal} />;
						})}
					</div> */}
				</motion.div>
			) : (
				<l-spiral size={45} color="coral"></l-spiral>
			)}
		</div>
	);
};

export default HomePage;

interface CheckInComponentProps extends HTMLAttributes<HTMLDivElement> {
	checkIn: CheckIn;
}

const CheckInComponent = ({ checkIn }: CheckInComponentProps) => {
	// const isInvalidPhone = React.useMemo(() => {
	// 	if ((shamers[index]?.phoneNumber ?? "") === "") return false;

	// 	return validatePhone(shamers[index]?.phoneNumber ?? "") ? false : true;
	// }, [shamers[index]]);

	const emojis = ["😀", "😂", "🥺", "😍", "😒", "👍", "🚀", "🌈", "🎉", "💔"];
	const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];

	return (
		<motion.div
			initial={{ opacity: 0, scale: 0.9 }}
			animate={{ opacity: 1, scale: 1 }}
			exit={{ opacity: 0, scale: 0.9 }}
			transition={{ duration: 0.3 }}
			className="w-full p-5 flex flex-col lg:flex-row  h-fit justify-center items-center transition-all duration-300 gap-4 rounded-2xl bg-default-100 hover:shadow-xl shadow-lg"
		>
			<div className="h-full w-fit -ml-10">
				<div className="text-6xl">{checkIn.icon ?? randomEmoji}</div>
			</div>
			<div className="h-full w-full   whitespace-pre flex items-center">
				<label className="flex flex-col h-full text-xs">
					Title
					<span className="font-bold text-xl !text-opacity-85">
						{checkIn?.title ?? "check in"}
					</span>
				</label>
			</div>
			{checkIn?.parentGoal?.title && (
				<div className="h-full w-full  whitespace-pre flex items-center">
					<label className="flex flex-col h-full text-xs">
						Goal
						<span className="font-bold text-xl">
							{checkIn?.parentGoal?.title}
						</span>
					</label>
				</div>
			)}
			<div className="h-full w-full  whitespace-pre flex items-center">
				<label className="flex flex-col h-full text-xs">
					Deadline
					<span className=" text-lg">
						{new Date(checkIn?.deadline as string).toLocaleDateString()}
					</span>
				</label>
			</div>
			<div className="h-full w-full flex items-center justify-around">
				<Button
					onClick={() => {
						client.graphql({
							query: updateCheckIn,
							variables: {
								input: {
									id: checkIn.id,
									status: "SUCCESSFUL",
								},
							},
						});
					}}
					className="h-
[truncated — 1314 more characters]
```

### src/app/goals/page.tsx

```typescript
"use client";

import React, { type FC, useState, useEffect, HTMLAttributes } from "react";
import { Input, Textarea } from "@nextui-org/react";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
import { Select, SelectItem } from "@nextui-org/react";
import { Button } from "@nextui-org/react";
import { Checkbox } from "@nextui-org/react";
import { generateClient } from "aws-amplify/api";
import { createCheckIn, createGoal, createShamer } from "@/graphql/mutations";
import {
	CheckInType,
	CreateCheckInInput,
	CreateGoalInput,
	CreateShamerInput,
	type Goal,
} from "@/API";
import { motion } from "framer-motion";
import { BsX, BsPlus } from "react-icons/bs";

import { toast } from "react-toastify";

import { v4 as uuidv4 } from "uuid";
import { twMerge } from "tailwind-merge";

const client = generateClient({});

type GoalsPageProps = {
	params?: {
		id: string;
	};
	searchParams?: {
		search?: string;
	};
};

const GoalsPage: FC<GoalsPageProps> = () => {
	const [goal, setGoal] = useState<Goal>({} as Goal);
	const [finished, setFinished] = useState(false);
	const [splineLoaded, setSplineLoaded] = useState(false);

	const ref1 = React.useRef(null);
	const ref2 = React.useRef(null);
	const ref3 = React.useRef(null);
	const ref4 = React.useRef(null);

	useEffect(() => {
		if (goal.id) {
			(ref2.current as any)?.scrollIntoView({ behavior: "smooth" });
		}
	}, [goal]);

	useEffect(() => {
		if (finished) {
			(ref3.current as any)?.scrollIntoView({ behavior: "smooth" });
		}
	}, [finished]);

	useEffect(() => {
		async function getLoader() {
			const { spiral } = await import("ldrs");
			spiral.register();
		}
		getLoader();
	}, []);

	return (
		<div className="w-full h-full flex flex-col justify-start  pb-40 items-center gap-20 p-5">
			{/* <img src="/logo1.png" alt="logo" className="w-1/4" /> */}

			<div
				ref={ref1}
				className="w-full h-full justify-center items-center flex pt-20"
			>
				<AddGoal setGoal={setGoal} />
			</div>
			<div
				ref={ref2}
				className="w-full h-full justify-center items-center flex pt-20 "
			>
				{goal.id && <AddShamers setFinished={setFinished} goal={goal} />}
				{/* <AddShamers goal={goal} /> */}
			</div>

			<motion.div
				initial={{ opacity: 0, y: 50 }}
				animate={{ opacity: 1, y: 0 }}
				exit={{ opacity: 0, y: 50 }}
				whileInView={{ opacity: 1, y: 0 }}
				ref={ref3}
				className="w-full h-full justify-center items-center flex flex-col pt-20 gap-5 font-bold text-3xl"
			>
				{finished && (
					<>
						{"Let's get shaming!"}
						<motion.img
							initial={{ opacity: 0, y: 50 }}
							animate={{ opacity: 1, y: 0 }}
							exit={{ opacity: 0, y: 50 }}
							whileInView={{ opacity: 1, y: 0 }}
							src="/irritated_icon.gif"
							className="rounded-full animate-in rotate-12 hover:-rotate-12 transition-all duration-300"
						/>
					</>
				)}
			</motion.div>
		</div>
	);
};

export default GoalsPage;

interface AddGoalProps extends HTMLAttributes<HTMLDivElement> {
	setGoal: React.Dispatch<React.SetStateAction<Goal>>;
}

const AddGoal = ({ setGoal }: AddGoalProps) => {
	const [goalInputForm, setGoalInputForm] = useState<CreateGoalInput>({});
	const [allowShaming, setAllowShaming] = useState(false);

	return (
		<motion.form
			initial={{ opacity: 0, y: 50 }}
			animate={{ opacity: 1, y: 0 }}
			exit={{ opacity: 0, y: 50 }}
			whileInView={{ opacity: 1, y: 0 }}
			className="lg:w-5/6 w-full h-fit rounded-lg p-5 gap-5 m-5 bg-white outline-zinc-200 outline  flex justify-start items-center flex-col  shadow-lg hover:shadow-xl transition-all duration-300"
			onSubmit={async (e) => {
				e.preventDefault();
				console.log("goalInputForm:", goalInputForm);
				await client
					.graphql({
						query: createGoal,
						variables: {
							input: {
								name: goalInputForm["title"],
								title: goalInputForm["title"],
								description: goalInputForm["description"],
								deadline: goalInputForm["deadline"],
								goalCreatorId: "demo",
								userGoalsId: "demo",
							} satisfies CreateGoalInput,
						},
					})
					.then((res) => {
						console.log(res);
						setGoal(res.data.createGoal as Goal);
					});
			}}
		>
			<div className="font-bold ">Add your goal</div>
			<Input
				label="What's your goal?"
				name="title"
				value={goalInputForm["title"] ?? ""}
				onChange={(e) => {
					setGoalInputForm({
						...goalInputForm,
						[e.target.name]: e.target.value,
					});
				}}
				classNames={{
					label: "text-black/50 dark:text-white/90 w-96",
					mainWrapper: ["w-full", "gap-2 bg-white"],
					input: [
						"bg-transparent",

						"text-black/90 dark:text-white/90",
						"placeholder:text-default-700/50 dark:placeholder:text-white/60",
					],
					innerWrapper: ["bg-transparent", "w-full"],
					inputWrapper: [
						"shadow-xl",
						"w-full",
						"bg-default-200/50",
						"dark:bg-default/60",
						"backdrop-blur-xl",
						"backdrop-saturate-200",
						"hover:bg-default-200/70",
						"dark:hover:bg-default/70",
						"group-data-[focused=true]:bg-default-200/50",
						"dark:group-data-[focused=true]:bg-default/60",
						"!cursor-text",
					],
				}}
				type="text"
				className="w-full bg-white"
			/>

			<Textarea
				label="Enter a description"
				name="description"
				value={goalInputForm["description"] ?? ""}
				onChange={(e) => {
					setGoalInputForm({
						...goalInputForm,
						[e.target.name]: e.target.value,
					});
				}}
				classNames={{
					label: "text-black/50 dark:text-white/90 w-96",
					mainWrapper: ["w-full", "gap-2"],
					input: [
						"bg-transparent",

						"text-black/90 dark:text-white/90",
						"placeholder:text-default-700/50 dark:placeholder:text-white/60",
					],
					innerWrapper: ["bg-transparent", "w-full"],
					inputWrapper: [
						"shadow-xl",
						"w-full",
						"bg-default
[truncated — 11323 more characters]
```

### src/app/api/send-text/route.ts

```typescript
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
import { type NextRequest, NextResponse } from "next/server";

export const dynamic = "force-dynamic";

const snsClient = new SNSClient({ region: "us-west-1" });
const params = {
	Message: "Your message text",
	PhoneNumber: "+14156056215",
};

export async function GET(req: NextRequest) {
	try {
		const data = await snsClient.send(new PublishCommand(params));
		console.log("Success.", data);
		return NextResponse.json({
			message: "Message sent successfully",
			messageId: data.MessageId,
		});
	} catch (err) {
		console.error(err);
		return NextResponse.json(
			{ error: "Failed to send message" },
			{ status: 500 }
		);
	}
}

```

### src/app/api/call-me/route.ts

```typescript
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
import { type NextRequest, NextResponse } from "next/server";

export const dynamic = "force-dynamic";

export async function POST(req: NextRequest) {
	try {
		const res = await req.json();

		const phoneNumber = res.phoneNumber;

		const message = res.message;

		console.log(res);
		console.log(phoneNumber, message);

		// Assuming these are the correct fields you want to send, replace <string> placeholders with actual values or variables.
		const bodyObject = {
			phone_number: phoneNumber, // Use the variable directly
			task: "scold the user for missing their goals, continue the conversation in a stern but playful manner to encourage them to finish their task. Speak like you're from texas and be a little rude please :)", // Replace <string> with actual task or variable
			model: "turbo", // Replace <string> with actual model or variable
			answered_by_enabled: true,
			reduce_latency: true,
			voice_id: 4,
			// voice_preset_id: "<string>", // Replace <string> with actual voice preset id or variable
			// start_time: "<string>", // Replace <string> with actual start time or variable
			// webhook: "<string>", // Replace <string> with actual webhook or variable
			wait_for_greeting: false,
			first_sentence: message, // Use the variable directly
			language: "eng", // Replace <string> with actual language or variable
			max_duration: "1.5", // Specify the actual max duration if needed
			amd: false,
			interruption_threshold: 50, // Specify the actual interruption threshold if needed
		};

		const options = {
			method: "POST",
			headers: {
				authorization: process.env.BLAND_API_KEY, // Replace <authorization> with actual authorization token or variable
				"Content-Type": "application/json",
			},
			body: JSON.stringify(bodyObject),
		};

		// @ts-ignore
		fetch("https://api.bland.ai/v1/calls", options)
			.then((response) => response.json())
			.then((response) => console.log(response))
			.catch((err) => console.error(err));

		return NextResponse.json({
			message: "Message sent successfully",
		});
	} catch (err) {
		console.error(err);
		return NextResponse.json(
			{ error: "Failed to send message" },
			{ status: 500 }
		);
	}
}

```

### amplify/backend/function/shamefullyNewUser/src/index.js

```javascript
/* Amplify Params - DO NOT EDIT
	API_SHAMEFULLY_GRAPHQLAPIENDPOINTOUTPUT
	API_SHAMEFULLY_GRAPHQLAPIIDOUTPUT
	API_SHAMEFULLY_GRAPHQLAPIKEYOUTPUT
	ENV
	REGION
Amplify Params - DO NOT EDIT */ /**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */

const https = require("https");
const AWS = require("aws-sdk");
const urlParse = require("url").URL;
const appsyncUrl = process.env.API_SHAMEFULLY_GRAPHQLAPIENDPOINTOUTPUT;
const region = process.env.REGION;
const endpoint = new urlParse(appsyncUrl).hostname.toString();
const apiKey = process.env.API_SHAMEFULLY_GRAPHQLAPIKEYOUTPUT;

const createUser = `
  mutation createUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
    }
  }
`;

exports.handler = async (event, context = { done: () => {} }) => {
	const userCreationReq = new AWS.HttpRequest(appsyncUrl, region);

	console.log("event", event);
	console.log("event.request.userAttributes", event.request.userAttributes);

	const { sub, given_name, family_name, email, phone_number } =
		event.request.userAttributes;

	console.log("event.request.userAttributes", event.request);

	const userInput = {
		input: {
			id: sub,
			name: given_name + " " + family_name,
			firstName: given_name,
			lastName: family_name,
			email: email,
			owner: sub,
			phoneNumber: phone_number,
		},
	};

	console.log(event.request.userAttributes);

	userCreationReq.method = "POST";
	userCreationReq.path = "/graphql";
	userCreationReq.headers.host = endpoint;
	userCreationReq.headers["Content-Type"] = "application/json";

	/* Create the user object */
	userCreationReq.body = JSON.stringify({
		query: createUser,
		operationName: "createUser",
		variables: userInput,
	});

	if (apiKey) {
		userCreationReq.headers["x-api-key"] = apiKey;
	} else {
		const userCreationSigner = new AWS.Signers.V4(
			userCreationReq,
			"appsync",
			true
		);
		userCreationSigner.addAuthorization(
			AWS.config.credentials,
			AWS.util.date.getDate()
		);

		const starterPacCreationSigner = new AWS.Signers.V4(
			starterPacCreationReq,
			"appsync",
			true
		);
		starterPacCreationSigner.addAuthorization(
			AWS.config.credentials,
			AWS.util.date.getDate()
		);
	}

	const userCreationPromise = new Promise((resolve, reject) => {
		const httpRequest = https.request(
			{ ...userCreationReq, host: endpoint },
			(result) => {
				let data = "";

				result.on("data", (chunk) => {
					data += chunk;
				});

				result.on("end", () => {
					resolve(JSON.parse(data.toString()));
				});
			}
		);

		httpRequest.write(userCreationReq.body);
		httpRequest.end();
	});

	const data = await Promise.all([userCreationPromise]);

	console.log(data.toString());
	if (context.done) context.done(null, event);
};

```

[56 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]