This is page 1 of 2. Use http://codebase.md/patrice-truong/cosmosdb-mcp?page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── assets
│ ├── architecture.drawio
│ ├── architecture.png
│ ├── constants_email.png
│ ├── cosmos-add-australia-east.png
│ ├── cosmos-add-region.png
│ ├── cosmos-backup-policy.png
│ ├── cosmos-basics.png
│ ├── cosmos-create-carts.png
│ ├── cosmos-create-products.png
│ ├── cosmos-create.png
│ ├── cosmos-enable-vector-search.png
│ ├── cosmos-encryption.png
│ ├── cosmos-global-distribution.png
│ ├── cosmos-helper-australia.png
│ ├── cosmos-multi-regions-write.png
│ ├── cosmos-networking.png
│ ├── cosmos-new-container.png
│ ├── cosmos-populate-products.png
│ ├── cosmos-products.png
│ ├── cosmos-rbac.png
│ ├── cosmos-validation.png
│ ├── demo.gif
│ ├── demo.png
│ ├── dotnet_build.png
│ ├── npm_run_build.png
│ ├── socket_server_ip.png
│ ├── storage-access-control.png
│ ├── storage-advanced.png
│ ├── storage-basics.png
│ ├── storage-blob-data-contributor.png
│ ├── storage-create-container.png
│ ├── storage-data-protection.png
│ ├── storage-encryption.png
│ ├── storage-files-uploaded.png
│ ├── storage-networking.png
│ ├── storage-review.png
│ ├── storage-role-assignment.png
│ ├── storage-upload-files.png
│ ├── storage-upload.png
│ ├── storage-validation.png
│ ├── vm-advanced.png
│ ├── vm-basics.png
│ ├── vm-disks.png
│ ├── vm-management.png
│ ├── vm-monitoring.png
│ ├── vm-networking.png
│ ├── vm-title.png
│ ├── vm-validation.png
│ └── vm-workload.png
├── LICENSE
├── mcp-server
│ ├── .env.template
│ ├── package-lock.json
│ ├── package.json
│ ├── run_mcp_server.cmd
│ ├── src
│ │ ├── aoai.ts
│ │ ├── cosmosdb-mcp-server.ts
│ │ ├── cosmosdb.ts
│ │ └── server.ts
│ └── tsconfig.json
├── nextjs
│ ├── .env.template
│ ├── .eslintrc.json
│ ├── app
│ │ ├── api
│ │ │ ├── blob-url
│ │ │ │ └── route.ts
│ │ │ ├── cart
│ │ │ │ └── route.ts
│ │ │ ├── chat
│ │ │ │ └── route.ts
│ │ │ ├── orders
│ │ │ │ ├── [id]
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ └── products
│ │ │ └── route.ts
│ │ ├── cart
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── orders
│ │ │ └── [id]
│ │ │ └── page.tsx
│ │ ├── page.tsx
│ │ └── products
│ │ └── page.tsx
│ ├── components
│ │ ├── AIAssistantDrawer.tsx
│ │ ├── CartItemCount.tsx
│ │ ├── Products.tsx
│ │ └── ui
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── breadcrumb.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── carousel.tsx
│ │ ├── chart.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── dialog.tsx
│ │ ├── drawer.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── hover-card.tsx
│ │ ├── input-otp.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── pagination.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── resizable.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── sonner.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle-group.tsx
│ │ ├── toggle.tsx
│ │ └── tooltip.tsx
│ ├── components.json
│ ├── context
│ │ └── CartContext.tsx
│ ├── hooks
│ │ └── use-toast.ts
│ ├── lib
│ │ ├── cosmosdb.ts
│ │ ├── mcp-client.ts
│ │ └── utils.ts
│ ├── models
│ │ ├── cart.ts
│ │ ├── cartContextType.ts
│ │ ├── cartItem.ts
│ │ ├── constants.ts
│ │ └── product.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ └── placeholder-300x300.png
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── populate
│ ├── catalog.json
│ ├── populate.csproj
│ ├── product.cs
│ ├── program.cs
│ └── set_rbac.ps1
└── README.md
```
# Files
--------------------------------------------------------------------------------
/nextjs/.eslintrc.json:
--------------------------------------------------------------------------------
```json
{
"extends": "next/core-web-vitals"
}
```
--------------------------------------------------------------------------------
/mcp-server/.env.template:
--------------------------------------------------------------------------------
```
AZURE_COSMOSDB_NOSQL_ENDPOINT=https://<cosmosdb_account>.documents.azure.com:443/
AZURE_COSMOSDB_NOSQL_DATABASE=eshop
AZURE_COSMOSDB_NOSQL_PRODUCTS_CONTAINER=products
AZURE_COSMOSDB_NOSQL_CARTS_CONTAINER=carts
AZURE_COSMOSDB_NOSQL_ORDERS_CONTAINER=orders
NEXT_PUBLIC_AZURE_TENANT_ID=<tenant_id>
NEXT_PUBLIC_AZURE_CLIENT_ID=<client_id>
NEXT_PUBLIC_AZURE_CLIENT_SECRET=<client_secret>
NEXT_PUBLIC_AZURE_STORAGE_ACCOUNT_NAME=<storage_account_name>
NEXT_PUBLIC_AZURE_STORAGE_CONTAINER_NAME=img
AZURE_OPENAI_ENDPOINT=https://<azure_openai_account>.openai.azure.com/
AZURE_OPENAI_API_KEY=<azure_openai_key>
AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small
AZURE_OPENAI_API_VERSION=2024-05-01-preview
```
--------------------------------------------------------------------------------
/nextjs/.env.template:
--------------------------------------------------------------------------------
```
AZURE_COSMOSDB_NOSQL_ENDPOINT=https://<cosmosdb_account>.documents.azure.com:443/
AZURE_COSMOSDB_NOSQL_DATABASE=eshop
AZURE_COSMOSDB_NOSQL_PRODUCTS_CONTAINER=products
AZURE_COSMOSDB_NOSQL_CARTS_CONTAINER=carts
AZURE_COSMOSDB_NOSQL_ORDERS_CONTAINER=orders
NEXT_PUBLIC_AZURE_TENANT_ID=<tenant_id>
NEXT_PUBLIC_AZURE_CLIENT_ID=<client_id>
NEXT_PUBLIC_AZURE_CLIENT_SECRET=<client_secret>
NEXT_PUBLIC_AZURE_STORAGE_ACCOUNT_NAME=<storage_account_name>
NEXT_PUBLIC_AZURE_STORAGE_CONTAINER_NAME=img
AZURE_OPENAI_ENDPOINT=https://<azure_openai_account>.openai.azure.com/
AZURE_OPENAI_API_KEY=<azure_openai_key>
AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small
AZURE_OPENAI_API_VERSION=2024-05-01-preview
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Created by https://www.gitignore.io/api/visualstudio
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
*.dll
*.pdb
*.cachefile
*.bak
*.ide*
.terraform/
.angular
.next
cache
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.cache
.angular
.env
.venv
no_commit
appsettings.json
Tags/
tmp/
deploy/
patoche/
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
dist/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Uncomment the next line to ignore your web deploy settings.
# By default, sensitive information, such as encrypted password
# should be stored in the .pubxml.user file.
#*.pubxml
*.pubxml.user
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Typescript v1 declaration files
typings/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
### VisualStudio Patch ###
# By default, sensitive information, such as encrypted password
# should be stored in the .pubxml.user file.
# End of https://www.gitignore.io/api/visualstudio
TestsWeb/debug.log
venv.ps1
*.code-workspace
run.cmd
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Azure Cosmos DB MCP CLient & Server
This repository contains a project that shows how to create an MCP Server and client for Azure Cosmos DB. The project is divided into 2 parts:
- Frontend application: NextJS 15 application that displays a products catalog and features an AI Assistant that helps users to find products in the catalog and get past orders
- an MCP Server component, connected to the Azure Cosmos DB NoSQL database and responsible for reading products and orders from the database.

## Azure Architecture
- an Azure Cosmos DB NoSQL database that stores the product catalog
- a node.js server that serves as the MCP Server component
## References
- [Create an Azure Cosmos DB for NoSQL account](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/quickstart-portal)
- [Create an Azure Storage account](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal)
- [Create a Windows virtual machine](https://learn.microsoft.com/en-us/azure/virtual-machines/windows/quick-create-portal)
- [Windows execution policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-7.5)
## Step-by-step walkthrough
### Installation
**Azure Cosmos DB**
In the Azure portal, create an Azure Cosmos DB for NoSQL account.
- Give a unique name for your Azure Cosmos DB account. We will be using cosmos-eastus2-nosql-2 in the rest of this walkthrough.

- Click on "Next: Global distribution"

- Accept the default values and click on "Next: Networking"

- Accept the default values and click on "Next: Backup Policy"
- Select "Periodic" backup policy
- Select "Locally-redundant backup storage"

- Click on "Next: Encryption"

- Click on "Review and Create" to start validation

- Click on "Create" to start the creation of the Azure Cosmos DB for NoSQL account
For this project, you will need to enable vector support on the Azure Cosmos DB account.
- In the settings section, select Features, then "Vector Search for NoSQL API"
- In the panel that opens, click on the Enable button

- Create the Azure Cosmos DB eShop database and the Products container
- Click on "..." next to eShop to display the contextual menu and select "New container" to create the "carts" container in the eShop database.
Make sure that the partition key is **_"/id"_** (the partition key is case-sensitive)
Expand "Container Vector Policy" and click on the "Add vector embedding" button

- Create the carts container

**Storage account**
1. Create a storage account to store the product images
For more details, refer to the documentation: https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal






**Install software pre-requisites **
1. Create a virtual machine in Azure or use your local computer
2. Install node.js v22.13.1 (LTS) from https://nodejs.org/en/download
3. Install Visual Studio Code x64 1.97.0 from https://code.visualstudio.com/download
4. Install Git 2.47.12 x64 from https://git-scm.com/downloads
5. Install .NET SDK x64 v9.0.102 from https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-9.0.102-windows-x64-installer
6. Open a terminal window and add nuget source with
```sh
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
```
7. If necessary, change PowerShell execution policies for Windows computers. Open a Powershell window **in administrator mode** and run this command
```sh
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
8. If necessary, install nuget, powershell, az cli and az modules
```sh
# install az cli
winget install -e --id Microsoft.AzureCLI
# install nuget and reference nuget source
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
# update to latest Powershell release (7.5 as of writing)
winget install --id Microsoft.PowerShell --source winget
# install az modules
Install-Module -Name Az -Repository PSGallery -Force -AllowClobber
```
9. Open a terminal window and clone the repository:
```sh
git clone https://github.com/patrice-truong/cosmosdb-mcp.git
cd cosmosdb-mcp
```
10. Navigate to the nextjs folder and install dependencies
```sh
cd cosmosdb-mcp/nextjs
npm install --legacy-peer-deps
```
11. In the nextjs folder, create and configure an .env file with the following values:
```sh
AZURE_COSMOSDB_NOSQL_ENDPOINT=https://<cosmosdb_account_name>.documents.azure.com:443/
AZURE_COSMOSDB_NOSQL_DATABASE=eshop
AZURE_COSMOSDB_NOSQL_PRODUCTS_CONTAINER=products
AZURE_COSMOSDB_NOSQL_CARTS_CONTAINER=carts
AZURE_COSMOSDB_NOSQL_ORDERS_CONTAINER=orders
AZURE_STORAGE_ACCOUNT_NAME=<storage_account_name>
AZURE_STORAGE_CONTAINER_NAME=<container_name>
```
12. Get your tenant ID. The tenant ID can be retrieved with this command:
```sh
az login
az account show --query tenantId -o tsv
```
13. In the webapi folder, configure the appsettings.json file and replace the tenant_id with the value obtained in the previous step:
```sh
{
"CosmosDb": {
"Endpoint": "https:/<cosmosdb_account_name>.documents.azure.com:443/",
"TenantId": "<tenant_id>",
"DatabaseName": "eshop",
"ProductsContainerName": "products",
"CartsContainerName": "carts",
"OrdersContainerName": "orders"
},
"AzureBlobStorage": {
"AccountName": "<storage_account_name>"
}
}
```
14. Create an app registration in the Azure Portal
15. Create an app secret in the Azure Portal
16. You will need to allow your app to get access to Azure Cosmos DB. Retrieve the 4 ids mentioned below and modify the file "populate/set_rbac.ps1".
| Variable | Reference |
| ---------------------------- | ------------------------------------------------- |
| Subscription Id | Cosmos DB > Overview > Subscription Id |
| Azure Cosmos DB account name | cosmos-eastus2-nosql-2 |
| Resource group name | Cosmos DB > Overview > Resource group name |
| Principal Id | App registration Object Id |
```sh
$SubscriptionId = "<subscription-id>" # Azure subscription id
$AccountName = "<cosmosdb-account-name>" # cosmos db account name
$ResourceGroupName = "<resource-group-name>" # resource group name of the Cosmos DB account
$PrincipalId = "<principal-id>" # object id of the app registered in Entra ID
```
17. Open a Powershell prompt, run Connect-AzAccount and execute ./set_rbac.ps1

18. Allow your app (or virtual machine) to access the storage account
- In the Azure portal, goto your storage account
- Select Access Control (IAM) in the menu

- Click on "Add role assignment"
- In the filter textbox, type "Storage Blob Data Contributor"

- Click on "Members"
- Select the name of your application

- Click on the "Select" button
- Click on "Review and assign"

19. Create a container and copy the content of the "azure-storage" folder to your storage account



20. Build webapi backend project with dotnet build
```sh
cd webapi
dotnet build
```
 18. On your secondary region VM (Australia East), modify the .env file with the IP address of the socket server in your primary region (East US 2)

19. There is no authentication built into this project. The user email is hard-coded in /nextjs/models/constants.ts. Change it to suit your demo needs

19. In mcp-server and nextjs folders, copy .env.template to .env and modify the values to suit your demo needs
```json
AZURE_COSMOSDB_NOSQL_ENDPOINT=https://<cosmosdb_account>.documents.azure.com:443/
AZURE_COSMOSDB_NOSQL_DATABASE=eshop
AZURE_COSMOSDB_NOSQL_PRODUCTS_CONTAINER=products
AZURE_COSMOSDB_NOSQL_CARTS_CONTAINER=carts
AZURE_COSMOSDB_NOSQL_ORDERS_CONTAINER=orders
NEXT_PUBLIC_AZURE_TENANT_ID=<tenant_id>
NEXT_PUBLIC_AZURE_CLIENT_ID=<client_id>
NEXT_PUBLIC_AZURE_CLIENT_SECRET=<client_secret>
NEXT_PUBLIC_AZURE_STORAGE_ACCOUNT_NAME=<storage_account_name>
NEXT_PUBLIC_AZURE_STORAGE_CONTAINER_NAME=img
AZURE_OPENAI_ENDPOINT=https://<azure_openai_account>.openai.azure.com/
AZURE_OPENAI_API_KEY=<azure_openai_key>
AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small
AZURE_OPENAI_API_VERSION=2024-05-01-preview
```
20. Build nextjs frontend project
```sh
cd nextjs
npm run build
```

## Populate the products catalog
In this section, we'll read the products catalog from the populate/catalog.json file and populate the Azure Cosmos DB for NoSQL database
1. Modify appsettings.json with your cosmosdb account name and
```json
{
"CosmosDb": {
"Endpoint": "https://<cosmosdb_account_name>.documents.azure.com:443/",
"TenantId": "<tenant_id>",
"DatabaseName": "eshop",
"ProductsContainerName": "products",
"OrdersContainerName": "orders",
}
}
```
2. Open a terminal window, navigate to the populate folder, execute az login, then dotnet run

3. Verify that the Azure Cosmos DB container has been properly populated

## Demo script
**Demo initialization:**
3. On your development computer, start the mcp server
```sh
cd mcp-server
npx ts-node src/server.ts
```
3. Start the front end project
- NextJS front end (store front)
- cd nextjs
- npm start
4. Optionally, open a command prompt and start the MCP inspector with this command:
npx -y @modelcontextprotocol/inspector
**Demo steps:**
1. Navigate to http://localhost:3002.
2.
2. Click on AI Assistant icon in the top right corner
3. Enter "I'm interested in backpacks" (the list of product refreshes with a list of backpacks)
4. Enter "Get my orders" (the list of orders refreshes with a list of orders)

```
--------------------------------------------------------------------------------
/mcp-server/run_mcp_server.cmd:
--------------------------------------------------------------------------------
```
npx ts-node src/server.ts
```
--------------------------------------------------------------------------------
/nextjs/models/constants.ts:
--------------------------------------------------------------------------------
```typescript
// /models/constants.ts
export const EMAIL = '[email protected]'
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"dependencies": {
"@types/uuid": "^10.0.0",
"uuid": "^11.1.0"
}
}
```
--------------------------------------------------------------------------------
/nextjs/postcss.config.js:
--------------------------------------------------------------------------------
```javascript
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
```
--------------------------------------------------------------------------------
/nextjs/models/cart.ts:
--------------------------------------------------------------------------------
```typescript
import { CartItem } from "./cartItem"
export type Cart = {
userName: string
items: CartItem[]
}
```
--------------------------------------------------------------------------------
/nextjs/models/cartItem.ts:
--------------------------------------------------------------------------------
```typescript
export type CartItem = {
id: number
name: string
price: number
quantity: number,
imageUrl: string
}
```
--------------------------------------------------------------------------------
/nextjs/app/page.tsx:
--------------------------------------------------------------------------------
```typescript
import { redirect } from "next/navigation";
export default function Home() {
redirect("http://localhost:3002/products");
}
```
--------------------------------------------------------------------------------
/nextjs/models/product.ts:
--------------------------------------------------------------------------------
```typescript
export type Product = {
id: string;
type: string;
brand: string;
description: string;
name: string;
price: number;
imageUrl: string;
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio';
const AspectRatio = AspectRatioPrimitive.Root;
export { AspectRatio };
```
--------------------------------------------------------------------------------
/nextjs/lib/utils.ts:
--------------------------------------------------------------------------------
```typescript
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
--------------------------------------------------------------------------------
/nextjs/app/cart/layout.tsx:
--------------------------------------------------------------------------------
```typescript
export const dynamic = 'force-dynamic'
export const revalidate = 0
export default function CartLayout({
children,
}: {
children: React.ReactNode
}) {
return children
}
```
--------------------------------------------------------------------------------
/nextjs/next-env.d.ts:
--------------------------------------------------------------------------------
```typescript
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
```
--------------------------------------------------------------------------------
/nextjs/models/cartContextType.ts:
--------------------------------------------------------------------------------
```typescript
import { CartItem } from "./cartItem"
export type CartContextType = {
items: CartItem[]
addItem: (item: Omit<CartItem, 'quantity'>) => void
removeItem: (id: number) => void
updateQuantity: (id: number, quantity: number) => void
clearCart: () => void
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
```typescript
import { cn } from '@/lib/utils';
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn('animate-pulse rounded-md bg-muted', className)}
{...props}
/>
);
}
export { Skeleton };
```
--------------------------------------------------------------------------------
/populate/product.cs:
--------------------------------------------------------------------------------
```csharp
public class Product
{
public int id { get; set; }
public string type { get; set; }
public string brand { get; set; }
public string name { get; set; }
public string description { get; set; }
public decimal price { get; set; }
public float[] embedding { get; set;}
}
```
--------------------------------------------------------------------------------
/mcp-server/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
```
--------------------------------------------------------------------------------
/nextjs/next.config.js:
--------------------------------------------------------------------------------
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
AZURE_STORAGE_ACCOUNT_NAME: process.env.AZURE_STORAGE_ACCOUNT_NAME,
AZURE_STORAGE_CONTAINER_NAME: process.env.AZURE_STORAGE_CONTAINER_NAME,
},
// output: 'export',
eslint: {
ignoreDuringBuilds: true,
},
images: { unoptimized: true },
};
module.exports = nextConfig;
```
--------------------------------------------------------------------------------
/nextjs/components.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
```
--------------------------------------------------------------------------------
/nextjs/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/mcp-server/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-server",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node dist/server.js",
"dev": "nodemon src/server.ts",
"build": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@azure/cosmos": "^4.2.0",
"@azure/identity": "^4.8.0",
"@azure/openai": "^2.0.0",
"@modelcontextprotocol/sdk": "^1.8.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.1",
"@types/node": "^22.13.13",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"nodemon": "^3.1.9",
"openai": "^4.90.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/label.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
);
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };
```
--------------------------------------------------------------------------------
/nextjs/components/CartItemCount.tsx:
--------------------------------------------------------------------------------
```typescript
'use client'
import Link from 'next/link'
import { ShoppingCart } from 'lucide-react'
import io from 'socket.io-client'
import { useCart } from '@/context/CartContext'
import { useEffect } from 'react'
export default function CartItemCount () {
const { items } = useCart()
const itemCount = items.reduce((total, item) => total + item.quantity, 0)
return (
<Link href='/cart' className='relative p-2 hover:bg-gray-100 rounded-full'>
<ShoppingCart className='h-6 w-6' />
{itemCount > 0 && (
<span className='absolute top-0 right-0 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-red-100 bg-red-600 rounded-full'>
{itemCount}
</span>
)}
</Link>
)
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/separator.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import { cn } from '@/lib/utils';
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = 'horizontal', decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
'shrink-0 bg-border',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className
)}
{...props}
/>
)
);
Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
);
}
);
Textarea.displayName = 'Textarea';
export { Textarea };
```
--------------------------------------------------------------------------------
/nextjs/app/api/orders/route.ts:
--------------------------------------------------------------------------------
```typescript
import { NextRequest, NextResponse } from 'next/server';
import { cosmosDBService } from '@/lib/cosmosdb';
export async function POST(request: NextRequest) {
try {
const order = await request.json();
if (!order || !order.items || !order.total) {
return NextResponse.json(
{ error: 'Invalid order data' },
{ status: 400 }
);
}
const { resources: items } = await cosmosDBService.database
.container('orders')
.items.create(order);
return NextResponse.json({
message: 'Order created successfully',
orderId: order.id
});
} catch (error) {
console.error('Error creating order:', error);
return NextResponse.json(
{ error: 'Failed to create order' },
{ status: 500 }
);
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import { useToast } from '@/hooks/use-toast';
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from '@/components/ui/toast';
export function Toaster() {
const { toasts } = useToast();
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/progress.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import { cn } from '@/lib/utils';
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
'relative h-4 w-full overflow-hidden rounded-full bg-secondary',
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress };
```
--------------------------------------------------------------------------------
/nextjs/app/api/orders/[id]/route.ts:
--------------------------------------------------------------------------------
```typescript
import { NextRequest, NextResponse } from 'next/server';
import { cosmosDBService } from '@/lib/cosmosdb';
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const { resources: items } = await cosmosDBService.ordersContainer.items
.query({
query: 'SELECT * FROM c WHERE c.id = @id',
parameters: [{ name: '@id', value: params.id }]
})
.fetchAll();
if (items.length === 0) {
return NextResponse.json({ error: 'Order not found' }, { status: 404 });
}
return NextResponse.json({
data: items[0],
statusCode: 200
});
} catch (error) {
console.error('Error fetching order:', error);
return NextResponse.json(
{ error: 'Failed to fetch order' },
{ status: 500 }
);
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/input.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = 'Input';
export { Input };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import { useTheme } from 'next-themes';
import { Toaster as Sonner } from 'sonner';
type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme();
return (
<Sonner
theme={theme as ToasterProps['theme']}
className="toaster group"
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'group-[.toast]:text-muted-foreground',
actionButton:
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton:
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
},
}}
{...props}
/>
);
};
export { Toaster };
```
--------------------------------------------------------------------------------
/mcp-server/src/aoai.ts:
--------------------------------------------------------------------------------
```typescript
import * as dotenv from "dotenv";
import { AzureOpenAI } from "openai";
const FILE_NAME = "src/aoai.ts";
/**
* Get embeddings from Azure OpenAI
* @param {string} q
* @returns {Promise<number[]>}
*/
export const getEmbeddingsAsync = async (q: string) => {
try {
console.log(`[${FILE_NAME}] q: ${q}`);
dotenv.config();
console.log(`[${FILE_NAME}] Embedding model=${process.env.AZURE_OPENAI_EMBEDDING_MODEL!}`);
const client = new AzureOpenAI({
endpoint: process.env.AZURE_OPENAI_ENDPOINT,
apiKey: process.env.AZURE_OPENAI_API_KEY!,
apiVersion: "2024-12-01-preview",
});
const embeddingResponse = await client.embeddings.create({
model: process.env.AZURE_OPENAI_EMBEDDING_MODEL!,
input: q,
});
const [{ embedding }] = embeddingResponse.data;
// console.log(`[${FILE_NAME}] Embeddings: ${JSON.stringify(embedding.slice(0, 50))}...`)
return embedding;
} catch (error) {
console.error(`[${FILE_NAME}] Error getting embeddings: ${JSON.stringify(error)}`);
}
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import { cn } from '@/lib/utils';
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/slider.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as SliderPrimitive from '@radix-ui/react-slider';
import { cn } from '@/lib/utils';
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
'relative flex w-full touch-none select-none items-center',
className
)}
{...props}
>
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background 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" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;
export { Slider };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/badge.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
}
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/switch.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as SwitchPrimitives from '@radix-ui/react-switch';
import { cn } from '@/lib/utils';
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0'
)}
/>
</SwitchPrimitives.Root>
));
Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
```
--------------------------------------------------------------------------------
/mcp-server/src/server.ts:
--------------------------------------------------------------------------------
```typescript
import 'dotenv/config'
import express, { Request, Response } from 'express'
import { CosmosDBMcpServer } from './cosmosdb-mcp-server'
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
import cors from 'cors'
const app = express()
// Add CORS middleware
app.use(cors({
origin: 'http://localhost:3002',
credentials: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type']
}))
const cosmosDBMcpServer = new CosmosDBMcpServer()
const server = cosmosDBMcpServer.getServer()
const transports: { [sessionId: string]: SSEServerTransport } = {}
app.get('/sse', async (_: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res)
transports[transport.sessionId] = transport
res.on('close', () => {
delete transports[transport.sessionId]
})
await server.connect(transport)
})
app.post('/messages', async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string
const transport = transports[sessionId]
if (transport) {
await transport.handlePostMessage(req, res)
} else {
res.status(400).send('No transport found for sessionId')
}
})
app.listen(3001)
```
--------------------------------------------------------------------------------
/nextjs/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import { cn } from '@/lib/utils';
const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/popover.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/lib/utils';
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
```
--------------------------------------------------------------------------------
/nextjs/lib/mcp-client.ts:
--------------------------------------------------------------------------------
```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
export class MCPClient {
private client: Client | null = null;
private initialize = async () => {
if (this.client) return;
this.client = new Client(
{
name: "cosmosdb-client",
version: "1.0.0"
},
{
capabilities: {
prompts: {},
resources: {},
tools: {}
}
}
);
const transport = new SSEClientTransport(
new URL("/sse", "http://localhost:3001/"),
{
requestInit: {
headers: {
'Content-Type': 'text/event-stream',
}
}
}
);
try {
await this.client.connect(transport);
} catch (e) {
console.error('Failed to connect to MCP server:', e);
this.client = null;
}
};
getClient = async (): Promise<Client | null> => {
await this.initialize();
return this.client;
};
static create = async (): Promise<MCPClient> => {
const instance = new MCPClient();
await instance.initialize();
return instance;
};
}
export const createMCPClient = async () => {
const mcpClient = await MCPClient.create();
return mcpClient.getClient();
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
```typescript
'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<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className
)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className
)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const toggleVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
{
variants: {
variant: {
default: 'bg-transparent',
outline:
'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-10 px-3',
sm: 'h-9 px-2.5',
lg: 'h-11 px-5',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
));
Toggle.displayName = TogglePrimitive.Root.displayName;
export { Toggle, toggleVariants };
```
--------------------------------------------------------------------------------
/populate/set_rbac.ps1:
--------------------------------------------------------------------------------
```
$SubscriptionId = "<subscription_id>" # Azure subscription id
$AccountName = "<cosmosdb_account_name>" # cosmos db account name
$ResourceGroupName = "rg-cosmosdb" # resource group name of the Cosmos DB account
$PrincipalId = "481510d9-3e2b-4582-a356-ffb8bca58b71" # id of the virtual machine in Entra ID
# Assign the "Cosmos DB Built-in Data Reader" role to an identity
$parameters = @{
ResourceGroupName = $ResourceGroupName
AccountName = $AccountName
RoleDefinitionId = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DocumentDB/databaseAccounts/$AccountName/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001"
PrincipalId = $PrincipalId
Scope = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DocumentDB/databaseAccounts/$AccountName"
}
New-AzCosmosDBSqlRoleAssignment @parameters
# Assign the "Cosmos DB Built-in Data Contributor" role to an identity
$parameters = @{
ResourceGroupName = $ResourceGroupName
AccountName = $AccountName
RoleDefinitionId = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DocumentDB/databaseAccounts/$AccountName/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002"
PrincipalId = $PrincipalId
Scope = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DocumentDB/databaseAccounts/$AccountName"
}
New-AzCosmosDBSqlRoleAssignment @parameters
```
--------------------------------------------------------------------------------
/nextjs/app/api/products/route.ts:
--------------------------------------------------------------------------------
```typescript
import { NextRequest, NextResponse } from 'next/server';
import { SqlQuerySpec } from '@azure/cosmos';
import { cosmosDBService } from '@/lib/cosmosdb';
export async function GET(request: NextRequest) {
const startTime = Date.now();
try {
// Get query parameters
const searchParams = request.nextUrl.searchParams;
const token = searchParams.get('token');
const ids = searchParams.get('ids');
let result;
if (ids) {
// If IDs are provided, fetch only those products
const idArray = ids.split(',');
const sqlQuerySpec: SqlQuerySpec = {
query: 'SELECT c.id, c.type, c.brand, c.name, c.description, c.price FROM c WHERE ARRAY_CONTAINS(@ids, c.id)',
parameters: [
{
name: '@ids',
value: idArray
}
]
};
const { resources: items } = await cosmosDBService.productsContainer.items
.query(sqlQuerySpec)
.fetchAll();
const duration = Date.now() - startTime;
result = {
data: items,
duration,
token
};
} else {
// Otherwise, fetch all products using the existing service method
result = await cosmosDBService.getProducts();
}
return NextResponse.json(result);
} catch (error) {
console.error('Error fetching products:', error);
return NextResponse.json(
{ error: 'Failed to fetch products' },
{ status: 500 }
);
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { Circle } from 'lucide-react';
import { cn } from '@/lib/utils';
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn('grid gap-2', className)}
{...props}
ref={ref}
/>
);
});
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
});
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
export { RadioGroup, RadioGroupItem };
```
--------------------------------------------------------------------------------
/nextjs/app/products/page.tsx:
--------------------------------------------------------------------------------
```typescript
import { Button } from "@/components/ui/button";
import Image from "next/image";
import Products from "@/components/Products";
type Props = {
searchParams: Promise<{
ids?: string;
}>;
};
export default async function ProductsPage({ searchParams }: Props) {
// Wait for searchParams to resolve
const params = await searchParams;
const productIds = params?.ids?.split(",").filter(Boolean);
return (
<div className="min-h-screen bg-gray-50">
<div className="relative">
<div className="absolute inset-0">
<Image src="https://images.unsplash.com/photo-1551698618-1dfe5d97d256?ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80" alt="Winter mountain landscape" fill priority className="object-cover" />
<div className="absolute inset-0 bg-gray-900/40" />
</div>
<div className="relative max-w-7xl mx-auto px-4 py-32 sm:px-6 lg:px-8">
<h1 className="text-4xl font-bold tracking-tight text-white sm:text-5xl lg:text-6xl">Ready for a new adventure?</h1>
<p className="mt-6 text-xl text-white max-w-3xl">Start the season with the latest in clothing and equipment.</p>
<div className="mt-10">
<Button size="lg" className="bg-white text-gray-900 hover:bg-gray-100">
Shop Now
</Button>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
<Products productIds={productIds} />
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/nextjs/app/api/chat/route.ts:
--------------------------------------------------------------------------------
```typescript
import { AzureOpenAI } from 'openai'
import { EMAIL } from '@/models/constants'
import { NextResponse } from 'next/server'
function validateEnvironmentVariables () {
const required = ['AZURE_OPENAI_ENDPOINT', 'AZURE_OPENAI_API_KEY']
for (const variable of required) {
if (!process.env[variable]) {
throw new Error(`Missing required environment variable: ${variable}`)
}
}
}
export async function POST (req: Request) {
try {
validateEnvironmentVariables()
const { message, conversationHistory, tools } = await req.json()
const messages = [
{
role: 'system',
content:
'You are a helpful shopping assistant that helps customers find products and answers questions about them.'
},
...conversationHistory.map((msg: any) => ({
role: msg.role,
content: msg.content
})),
{ role: 'user', content: message }
]
const client = new AzureOpenAI({
endpoint: process.env.AZURE_OPENAI_ENDPOINT,
apiKey: process.env.AZURE_OPENAI_API_KEY!,
apiVersion: process.env.AZURE_OPENAI_API_VERSION!
})
const response = await client.chat.completions.create({
messages: messages,
model: process.env.AZURE_OPENAI_CHAT_MODEL!,
tools: tools
})
const content = JSON.stringify(response)
console.log('Chat API Response:', content)
return NextResponse.json({ content })
} catch (error) {
console.error('Chat API Error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/alert.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const alertVariants = cva(
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: 'default',
},
}
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
));
AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
));
AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertTitle, AlertDescription };
```
--------------------------------------------------------------------------------
/nextjs/app/api/blob-url/route.ts:
--------------------------------------------------------------------------------
```typescript
import { BlobServiceClient } from "@azure/storage-blob";
import { ClientSecretCredential } from "@azure/identity";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const blobName = searchParams.get("blob");
if (!blobName) {
return NextResponse.json({ error: "Blob name is required" }, { status: 400 });
}
try {
const credential = new ClientSecretCredential(process.env.NEXT_PUBLIC_AZURE_TENANT_ID!, process.env.NEXT_PUBLIC_AZURE_CLIENT_ID!, process.env.NEXT_PUBLIC_AZURE_CLIENT_SECRET!);
const blobServiceClient = new BlobServiceClient(`https://${process.env.NEXT_PUBLIC_AZURE_STORAGE_ACCOUNT_NAME}.blob.core.windows.net`, credential);
const containerClient = blobServiceClient.getContainerClient(process.env.NEXT_PUBLIC_AZURE_STORAGE_CONTAINER_NAME!);
const blobClient = containerClient.getBlobClient(blobName);
// Download blob content
const downloadResponse = await blobClient.download();
const chunks: Uint8Array[] = [];
for await (const chunk of downloadResponse.readableStreamBody as AsyncIterable<Uint8Array>) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
const base64String = `data:image/webp;base64,${buffer.toString("base64")}`;
return NextResponse.json({ url: base64String });
} catch (error) {
console.error("Error accessing blob:", JSON.stringify(error, null, 2));
return NextResponse.json(
{
error: "Failed to access blob",
details: error instanceof Error ? error.message : "Unknown error",
},
{ status: 500 }
);
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
'flex touch-none select-none transition-colors',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent p-[1px]',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent p-[1px]',
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar };
```
--------------------------------------------------------------------------------
/nextjs/app/api/cart/route.ts:
--------------------------------------------------------------------------------
```typescript
import { NextRequest, NextResponse } from 'next/server';
import { cosmosDBService } from '@/lib/cosmosdb';
export async function GET(request: NextRequest) {
try {
// Get the userName from query parameters
const searchParams = request.nextUrl.searchParams;
const userName = searchParams.get('userName');
if (!userName) {
return NextResponse.json(
{ error: 'userName parameter is required' },
{ status: 400 }
);
}
// Load the cart from Cosmos DB
const result = await cosmosDBService.loadCart(userName);
if (result.error) {
return NextResponse.json(
{ error: result.error },
{ status: result.statusCode }
);
}
return NextResponse.json(result);
} catch (error) {
console.error('Error loading cart:', error);
return NextResponse.json(
{ error: 'Failed to load cart' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
// Get the cart data from the request body
const cart = await request.json();
if (!cart || !cart.userName) {
return NextResponse.json(
{ error: 'Invalid cart data. userName is required.' },
{ status: 400 }
);
}
// Store the cart in Cosmos DB
const result = await cosmosDBService.storeCart(cart);
if (result.error) {
return NextResponse.json(
{ error: result.error },
{ status: result.statusCode }
);
}
return NextResponse.json(result);
} catch (error) {
console.error('Error storing cart:', error);
return NextResponse.json(
{ error: 'Failed to store cart' },
{ status: 500 }
);
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/resizable.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import { GripVertical } from 'lucide-react';
import * as ResizablePrimitive from 'react-resizable-panels';
import { cn } from '@/lib/utils';
const ResizablePanelGroup = ({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
<ResizablePrimitive.PanelGroup
className={cn(
'flex h-full w-full data-[panel-group-direction=vertical]:flex-col',
className
)}
{...props}
/>
);
const ResizablePanel = ResizablePrimitive.Panel;
const ResizableHandle = ({
withHandle,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean;
}) => (
<ResizablePrimitive.PanelResizeHandle
className={cn(
'relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90',
className
)}
{...props}
>
{withHandle && (
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
<GripVertical className="h-2.5 w-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
);
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { toggleVariants } from '@/components/ui/toggle';
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: 'default',
variant: 'default',
});
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn('flex items-center justify-center gap-1', className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
));
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext);
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
);
});
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
export { ToggleGroup, ToggleGroupItem };
```
--------------------------------------------------------------------------------
/nextjs/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
import './globals.css';
import { Bot, Mountain, User } from 'lucide-react';
import AIAssistantDrawer from '@/components/AIAssistantDrawer';
import CartItemCount from '@/components/CartItemCount';
import { CartProvider } from '@/context/CartContext';
import { Inter } from 'next/font/google';
import Link from 'next/link';
import type { Metadata } from 'next';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'Northern Mountains',
description: 'Your adventure gear destination',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<CartProvider>
<div className="flex flex-col min-h-screen">
<header className="border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between">
<Link href="/" className="flex items-center space-x-2">
<Mountain className="h-8 w-8" />
<span className="font-bold text-xl">Northern Mountains</span>
<span className="font-bold text-xl text-red-500 pl-2">(East US 2)</span>
</Link>
<div className="flex items-center space-x-4">
<AIAssistantDrawer />
<Link href="/account" className="p-2 hover:bg-gray-100 rounded-full">
<User className="h-6 w-6" />
</Link>
<CartItemCount />
</div>
</div>
</div>
</header>
<main className="flex-grow relative">{children}</main>
</div>
</CartProvider>
</body>
</html>
);
}
```
--------------------------------------------------------------------------------
/mcp-server/src/cosmosdb.ts:
--------------------------------------------------------------------------------
```typescript
import { CosmosClient } from '@azure/cosmos'
import { DefaultAzureCredential } from '@azure/identity'
const sourceFile = 'src/cosmosdb.ts'
export function validateEnvironmentVariables () {
const required = [
'AZURE_COSMOSDB_NOSQL_ENDPOINT',
'AZURE_COSMOSDB_NOSQL_DATABASE',
'AZURE_COSMOSDB_NOSQL_PRODUCTS_CONTAINER',
'AZURE_COSMOSDB_NOSQL_CARTS_CONTAINER'
]
for (const variable of required) {
if (!process.env[variable]) {
throw new Error(`Missing required environment variable: ${variable}`)
}
}
}
export const initializeCosmosDB = () => {
const endpoint = process.env.AZURE_COSMOSDB_NOSQL_ENDPOINT as string
const databaseId = process.env.AZURE_COSMOSDB_NOSQL_DATABASE as string
const productsContainerId = process.env
.AZURE_COSMOSDB_NOSQL_PRODUCTS_CONTAINER as string
const cartsContainerId = process.env
.AZURE_COSMOSDB_NOSQL_CARTS_CONTAINER as string
const ordersContainerId = process.env
.AZURE_COSMOSDB_NOSQL_ORDERS_CONTAINER as string
const credential = new DefaultAzureCredential()
console.debug(
`[${sourceFile}::initializeCosmosDB] Cosmos DB endpoint: ${endpoint}`
)
console.debug(
`[${sourceFile}::initializeCosmosDB] Cosmos DB products container: ${productsContainerId}`
)
console.debug(
`[${sourceFile}::initializeCosmosDB] Cosmos DB carts container: ${cartsContainerId}`
)
console.debug(
`[${sourceFile}::initializeCosmosDB] Cosmos DB orders container: ${ordersContainerId}`
)
const client = new CosmosClient({
endpoint: endpoint,
aadCredentials: credential
})
const database = client.database(databaseId)
const productsContainer = database.container(productsContainerId)
const ordersContainer = database.container(ordersContainerId)
return { client, database, productsContainer, ordersContainer }
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/button.tsx:
--------------------------------------------------------------------------------
```typescript
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<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/card.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { cn } from '@/lib/utils';
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-lg border bg-card text-card-foreground shadow-sm',
className
)}
{...props}
/>
));
Card.displayName = 'Card';
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
));
CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
'text-2xl font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
));
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
));
CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
));
CardFooter.displayName = 'CardFooter';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
className
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };
```
--------------------------------------------------------------------------------
/nextjs/app/globals.css:
--------------------------------------------------------------------------------
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn('border-b', className)}
{...props}
/>
));
AccordionItem.displayName = 'AccordionItem';
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn('pb-4 pt-0', className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/input-otp.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import { OTPInput, OTPInputContext } from 'input-otp';
import { Dot } from 'lucide-react';
import { cn } from '@/lib/utils';
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
'flex items-center gap-2 has-[:disabled]:opacity-50',
containerClassName
)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
));
InputOTP.displayName = 'InputOTP';
const InputOTPGroup = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center', className)} {...props} />
));
InputOTPGroup.displayName = 'InputOTPGroup';
const InputOTPSlot = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
return (
<div
ref={ref}
className={cn(
'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'z-10 ring-2 ring-ring ring-offset-background',
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
);
});
InputOTPSlot.displayName = 'InputOTPSlot';
const InputOTPSeparator = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
));
InputOTPSeparator.displayName = 'InputOTPSeparator';
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
```
--------------------------------------------------------------------------------
/nextjs/tailwind.config.ts:
--------------------------------------------------------------------------------
```typescript
import type { Config } from 'tailwindcss';
const config: Config = {
darkMode: ['class'],
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))',
},
},
keyframes: {
'accordion-down': {
from: {
height: '0',
},
to: {
height: 'var(--radix-accordion-content-height)',
},
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)',
},
to: {
height: '0',
},
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
plugins: [require('tailwindcss-animate')],
};
export default config;
```
--------------------------------------------------------------------------------
/nextjs/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { DayPicker } from 'react-day-picker';
import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn('p-3', className)}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
nav: 'space-x-1 flex items-center',
nav_button: cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
),
nav_button_previous: 'absolute left-1',
nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell:
'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: 'h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
),
day_range_end: 'day-range-end',
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle:
'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/breadcrumb.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { ChevronRight, MoreHorizontal } from 'lucide-react';
import { cn } from '@/lib/utils';
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<'nav'> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = 'Breadcrumb';
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<'ol'>
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
className
)}
{...props}
/>
));
BreadcrumbList.displayName = 'BreadcrumbList';
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<'li'>
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn('inline-flex items-center gap-1.5', className)}
{...props}
/>
));
BreadcrumbItem.displayName = 'BreadcrumbItem';
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<'a'> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : 'a';
return (
<Comp
ref={ref}
className={cn('transition-colors hover:text-foreground', className)}
{...props}
/>
);
});
BreadcrumbLink.displayName = 'BreadcrumbLink';
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<'span'>
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn('font-normal text-foreground', className)}
{...props}
/>
));
BreadcrumbPage.displayName = 'BreadcrumbPage';
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<'li'>) => (
<li
role="presentation"
aria-hidden="true"
className={cn('[&>svg]:size-3.5', className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
BreadcrumbSeparator.displayName = 'BreadcrumbSeparator';
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<'span'>) => (
<span
role="presentation"
aria-hidden="true"
className={cn('flex h-9 w-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis';
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/pagination.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
import { cn } from '@/lib/utils';
import { ButtonProps, buttonVariants } from '@/components/ui/button';
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
<nav
role="navigation"
aria-label="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
);
Pagination.displayName = 'Pagination';
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<'ul'>
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
));
PaginationContent.displayName = 'PaginationContent';
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<'li'>
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn('', className)} {...props} />
));
PaginationItem.displayName = 'PaginationItem';
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, 'size'> &
React.ComponentProps<'a'>;
const PaginationLink = ({
className,
isActive,
size = 'icon',
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? 'page' : undefined}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size,
}),
className
)}
{...props}
/>
);
PaginationLink.displayName = 'PaginationLink';
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn('gap-1 pl-2.5', className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
);
PaginationPrevious.displayName = 'PaginationPrevious';
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn('gap-1 pr-2.5', className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
PaginationNext.displayName = 'PaginationNext';
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<'span'>) => (
<span
aria-hidden
className={cn('flex h-9 w-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
);
PaginationEllipsis.displayName = 'PaginationEllipsis';
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/table.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import { cn } from '@/lib/utils';
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
));
Table.displayName = 'Table';
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
));
TableHeader.displayName = 'TableHeader';
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
));
TableBody.displayName = 'TableBody';
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
className
)}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
className
)}
{...props}
/>
));
TableRow.displayName = 'TableRow';
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
className
)}
{...props}
/>
));
TableHead.displayName = 'TableHead';
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)}
{...props}
/>
));
TableCell.displayName = 'TableCell';
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn('mt-4 text-sm text-muted-foreground', className)}
{...props}
/>
));
TableCaption.displayName = 'TableCaption';
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};
```
--------------------------------------------------------------------------------
/nextjs/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "nextjs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 3002",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@azure/cosmos": "^4.2.0",
"@azure/identity": "^4.6.0",
"@azure/storage-blob": "^12.26.0",
"@fastify/cors": "^10.0.2",
"@hookform/resolvers": "^3.9.0",
"@modelcontextprotocol/sdk": "^1.8.0",
"@next/swc-wasm-nodejs": "13.5.1",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@types/node": "20.6.2",
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3",
"autoprefixer": "10.4.15",
"axios": "^1.7.9",
"azure-storage": "^2.10.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"dotenv": "^16.4.7",
"embla-carousel-react": "^8.3.0",
"eslint": "8.49.0",
"eslint-config-next": "15.1.6",
"express": "^5.0.1",
"input-otp": "^1.2.4",
"lucide-react": "^0.446.0",
"next": "15.1.6",
"next-themes": "^0.3.0",
"openai": "^4.90.0",
"postcss": "8.4.30",
"re-resizable": "^6.11.2",
"react": "19.0.0",
"react-day-picker": "^8.10.1",
"react-dom": "19.0.0",
"react-hook-form": "^7.53.0",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^2.1.3",
"recharts": "^2.12.7",
"sonner": "^1.5.0",
"tailwind-merge": "^2.5.2",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "5.2.2",
"uuid": "^11.0.5",
"vaul": "^0.9.9",
"zod": "^3.24.2"
},
"devDependencies": {},
"overrides": {
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3"
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/drawer.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';
import { cn } from '@/lib/utils';
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
);
Drawer.displayName = 'Drawer';
const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn('fixed inset-0 z-50 bg-black/80', className)}
{...props}
/>
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
className
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = 'DrawerContent';
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)}
{...props}
/>
);
DrawerHeader.displayName = 'DrawerHeader';
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
);
DrawerFooter.displayName = 'DrawerFooter';
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
};
```
--------------------------------------------------------------------------------
/nextjs/app/orders/[id]/page.tsx:
--------------------------------------------------------------------------------
```typescript
'use client'
import { useEffect, useState } from 'react'
import Image from 'next/image'
import { Button } from '@/components/ui/button'
import { useRouter } from 'next/navigation'
export default function OrderDetailsPage({ params }: { params: { id: string } }) {
const [order, setOrder] = useState<any>(null)
const [loading, setLoading] = useState(true)
const [imageUrls, setImageUrls] = useState<{ [key: string]: string }>({})
const router = useRouter()
useEffect(() => {
async function fetchOrder() {
try {
const response = await fetch(`/api/orders/${params.id}`)
if (response.ok) {
const data = await response.json()
setOrder(data.data)
// Fetch images for all items
data.data.items.forEach(async (item: any) => {
const imgResponse = await fetch(`/api/blob-url?blob=${item.id}.webp`, {
cache: 'force-cache'
})
const imgData = await imgResponse.json()
if (imgData.url) {
setImageUrls(prev => ({
...prev,
[item.id]: imgData.url
}))
}
})
}
} catch (error) {
console.error('Error fetching order:', error)
} finally {
setLoading(false)
}
}
fetchOrder()
}, [params.id])
if (loading) {
return <div>Loading order details...</div>
}
if (!order) {
return <div>Order not found</div>
}
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-8">
<h1 className="text-2xl font-bold mb-2">Order Confirmation</h1>
<p className="text-gray-600">Order #{order.id}</p>
<p className="text-gray-600">Status: {order.status}</p>
<p className="text-gray-600">Date: {new Date(order.createdAt).toLocaleDateString()}</p>
</div>
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
<h2 className="text-lg font-medium">Order Items</h2>
</div>
<div className="border-t border-gray-200">
{order.items.map((item: any) => (
<div key={item.id} className="flex items-center p-4 border-b">
<Image
src={imageUrls[item.id] || '/placeholder-300x300.png'}
alt={item.name}
className="w-20 h-20 object-cover rounded"
width={80}
height={80}
unoptimized
/>
<div className="ml-4 flex-1">
<h3 className="font-medium">{item.name}</h3>
<p className="text-gray-500">Quantity: {item.quantity}</p>
<p className="text-gray-500">${item.price} each</p>
</div>
<div className="text-right">
<p className="font-medium">${(item.price * item.quantity).toFixed(2)}</p>
</div>
</div>
))}
</div>
<div className="px-4 py-5 sm:px-6">
<div className="flex justify-between">
<span className="font-medium">Total</span>
<span className="font-medium">${order.total.toFixed(2)}</span>
</div>
</div>
</div>
<Button
className="mt-8"
onClick={() => router.push('/')}
>
Continue Shopping
</Button>
</div>
)
}
```
--------------------------------------------------------------------------------
/nextjs/context/CartContext.tsx:
--------------------------------------------------------------------------------
```typescript
// app/context/CartContext.tsx
'use client'
export const dynamic = 'force-dynamic' // Ensure dynamic rendering
import { createContext, useContext, useEffect, useState } from 'react'
import { Cart } from '@/models/cart'
import { CartContextType } from '@/models/cartContextType'
import { CartItem } from '@/models/cartItem'
import { EMAIL } from '@/models/constants'
export const CartContext = createContext<CartContextType | undefined>(undefined)
const prefix = '/context/CartContext.tsx'
export function CartProvider ({ children }: { children: React.ReactNode }) {
const [items, setItems] = useState<CartItem[]>([])
useEffect(() => {
async function loadCart () {
try {
const savedCart = await loadCartFromCosmosDB(EMAIL)
if (savedCart?.data?.items) {
console.log(`[${prefix}::loadCart]`, savedCart.data.items)
setItems(savedCart.data.items)
}
} catch (error) {
console.error('Error loading cart:', error)
}
}
loadCart()
}, []) // Remove isSocketUpdate dependency
useEffect(() => {
console.log('Items state changed:', items)
let isStale = false
const storeCart = async () => {
if (isStale) return
try {
const cart: Cart = {
userName: EMAIL,
items: items
}
await storeCartInCosmosDB(cart)
} catch (error) {
console.error('Error managing cart:', error)
}
}
storeCart()
return () => {
isStale = true
}
}, [items])
const addItem = (newItem: Omit<CartItem, 'quantity'>) => {
setItems(currentItems => {
const existingItem = currentItems.find(item => item.id === newItem.id)
if (existingItem) {
return currentItems.map(item =>
item.id === newItem.id
? { ...item, quantity: item.quantity + 1 }
: item
)
}
return [...currentItems, { ...newItem, quantity: 1 }]
})
}
const removeItem = (id: number) => {
setItems(currentItems => currentItems.filter(item => item.id !== id))
}
const updateQuantity = (id: number, quantity: number) => {
setItems(currentItems =>
currentItems.map(item => (item.id === id ? { ...item, quantity } : item))
)
}
const clearCart = () => {
setItems([])
}
// load cart from Cosmos DB
const loadCartFromCosmosDB = async (userName: string) => {
try {
const response = await fetch(`/api/cart?userName=${userName}`)
const result = await response.json()
console.log(`[${prefix}::loadCartFromCosmosDB] ${JSON.stringify(result)}`)
return result
} catch (error) {
console.error('Error fetching cart:', error)
return error
}
}
// Function to store cart in Cosmos DB
const storeCartInCosmosDB = async (cart: Cart) => {
try {
console.log(`[${prefix}::storeCartInCosmosDB] ${JSON.stringify(cart)}`)
const response = await fetch(`/api/cart`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Update': 'true'
},
body: JSON.stringify(cart)
})
return await response.json()
} catch (error) {
console.error('Error storing cart in Cosmos DB:', error)
throw error
}
}
return (
<CartContext.Provider
value={{ items, addItem, removeItem, updateQuantity, clearCart }}
>
{children}
</CartContext.Provider>
)
}
export function useCart () {
const context = useContext(CartContext)
if (context === undefined) {
throw new Error('useCart must be used within a CartProvider')
}
return context
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { cn } from '@/lib/utils';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-1.5 text-center sm:text-left',
className
)}
{...props}
/>
);
DialogHeader.displayName = 'DialogHeader';
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};
```
--------------------------------------------------------------------------------
/nextjs/components/Products.tsx:
--------------------------------------------------------------------------------
```typescript
// components/Products.tsx
'use client'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import Image from 'next/image'
import { Product } from '@/models/product'
import { useCart } from '@/context/CartContext'
import { useRouter } from 'next/navigation'
import { v4 as uuidv4 } from 'uuid' // Import the uuid library
const prefix = 'components/Products.tsx'
interface ProductsProps {
productIds?: string[];
}
export default function Products({ productIds }: ProductsProps) {
const { addItem } = useCart()
const router = useRouter()
const [products, setProducts] = useState<Product[]>([])
const [loading, setLoading] = useState(true)
const [imageUrls, setImageUrls] = useState<{ [key: string]: string }>({})
useEffect(() => {
async function fetchImageUrl (id: string) {
try {
const response = await fetch(`/api/blob-url?blob=${id}.webp`, {
cache: 'force-cache'
})
const data = await response.json()
if (data.url) {
setImageUrls(prev => ({
...prev,
[id]: data.url // This will now be a base64 string
}))
}
} catch (error) {
console.error('Error fetching image URL:', error)
}
}
async function fetchProducts () {
try {
const token = uuidv4()
let url = `/api/products?token=${token}`
// If productIds are provided, add them as a query parameter
if (productIds && productIds.length > 0) {
url += `&ids=${productIds.join(',')}`
}
const response = await fetch(url)
const result = await response.json()
console.log(`[${prefix}] fetchProducts: ${result.duration} ms `)
setProducts(result.data)
// Fetch image URLs for all products
for (const product of result.data) {
fetchImageUrl(product.id)
}
} catch (error) {
console.error('Error fetching products:', error)
} finally {
setLoading(false)
}
}
fetchProducts()
}, [productIds]) // Add productIds as a dependency
const handleAddToCart = (product: Product) => {
const cartItem = {
...product,
id: Number(product.id) // Convert id to number
}
addItem(cartItem)
// router.push('/cart')
}
if (loading) {
return (
<div className='grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-3 xl:gap-x-8'>
{[...Array(6)].map((_, i) => (
<div key={i} className='animate-pulse'>
<div className='aspect-w-1 aspect-h-1 w-full bg-gray-200 rounded-lg mb-4' />
<div className='h-4 bg-gray-200 rounded w-3/4 mb-2' />
<div className='h-4 bg-gray-200 rounded w-1/4' />
</div>
))}
</div>
)
}
return (
<div className='grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-3 xl:gap-x-8'>
{products.map(product => (
<div key={product.id} className='group relative'>
<div className='aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-lg bg-gray-200'>
<Image
src={imageUrls[product.id] || '/placeholder-300x300.png'}
alt={product.name}
className='h-full w-full object-cover object-center group-hover:opacity-75'
width={300}
height={300}
unoptimized
/>
</div>
<div className='mt-4 flex justify-between'>
<div>
<h3 className='text-sm text-gray-700'>{product.name}</h3>
<p className='mt-1 text-sm text-gray-500'>${product.price}</p>
</div>
</div>
<Button
onClick={() => handleAddToCart(product)}
className='mt-4 w-full'
>
Add to Cart
</Button>
</div>
))}
</div>
)
}
```
--------------------------------------------------------------------------------
/nextjs/hooks/use-toast.ts:
--------------------------------------------------------------------------------
```typescript
'use client';
// Inspired by react-hot-toast library
import * as React from 'react';
import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & {
id: string;
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
};
const actionTypes = {
ADD_TOAST: 'ADD_TOAST',
UPDATE_TOAST: 'UPDATE_TOAST',
DISMISS_TOAST: 'DISMISS_TOAST',
REMOVE_TOAST: 'REMOVE_TOAST',
} as const;
let count = 0;
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString();
}
type ActionType = typeof actionTypes;
type Action =
| {
type: ActionType['ADD_TOAST'];
toast: ToasterToast;
}
| {
type: ActionType['UPDATE_TOAST'];
toast: Partial<ToasterToast>;
}
| {
type: ActionType['DISMISS_TOAST'];
toastId?: ToasterToast['id'];
}
| {
type: ActionType['REMOVE_TOAST'];
toastId?: ToasterToast['id'];
};
interface State {
toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({
type: 'REMOVE_TOAST',
toastId: toastId,
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'ADD_TOAST':
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
};
case 'UPDATE_TOAST':
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
};
case 'DISMISS_TOAST': {
const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id);
});
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
};
}
case 'REMOVE_TOAST':
if (action.toastId === undefined) {
return {
...state,
toasts: [],
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
};
}
};
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] };
function dispatch(action: Action) {
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState);
});
}
type Toast = Omit<ToasterToast, 'id'>;
function toast({ ...props }: Toast) {
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
type: 'UPDATE_TOAST',
toast: { ...props, id },
});
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
dispatch({
type: 'ADD_TOAST',
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
},
},
});
return {
id: id,
dismiss,
update,
};
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => {
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1);
}
};
}, [state]);
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
};
}
export { useToast, toast };
```
--------------------------------------------------------------------------------
/nextjs/components/ui/form.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from 'react-hook-form';
import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>');
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn('space-y-2', className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = 'FormItem';
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && 'text-destructive', className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = 'FormLabel';
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = 'FormControl';
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
);
});
FormDescription.displayName = 'FormDescription';
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn('text-sm font-medium text-destructive', className)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = 'FormMessage';
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
```typescript
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 pointer-events-none",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
{
variants: {
side: {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
side: 'right',
},
}
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = 'right', className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
onPointerDownOutside={(e) => {
e.preventDefault();
}}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-2 text-center sm:text-left',
className
)}
{...props}
/>
);
SheetHeader.displayName = 'SheetHeader';
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}
/>
);
SheetFooter.displayName = 'SheetFooter';
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-foreground', className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};
```
--------------------------------------------------------------------------------
/nextjs/lib/cosmosdb.ts:
--------------------------------------------------------------------------------
```typescript
import { Container, CosmosClient, SqlQuerySpec } from "@azure/cosmos";
import { Cart } from "@/models/cart";
import { DefaultAzureCredential } from "@azure/identity";
const sourceFile: string = "lib/cosmosdb.ts";
export class CosmosDBService {
private client: CosmosClient;
public database: any;
public cartsContainer: Container;
public productsContainer: Container;
public ordersContainer: Container; // Add this line
constructor() {
const endpoint = process.env.AZURE_COSMOSDB_NOSQL_ENDPOINT as string;
const databaseId = process.env.AZURE_COSMOSDB_NOSQL_DATABASE as string;
const productsContainerId = process.env.AZURE_COSMOSDB_NOSQL_PRODUCTS_CONTAINER as string;
const cartsContainerId = process.env.AZURE_COSMOSDB_NOSQL_CARTS_CONTAINER as string;
const ordersContainerId = process.env.AZURE_COSMOSDB_NOSQL_ORDERS_CONTAINER as string;
const credential = new DefaultAzureCredential();
this.client = new CosmosClient({
endpoint: endpoint,
aadCredentials: credential,
});
this.database = this.client.database(databaseId);
this.cartsContainer = this.database.container(cartsContainerId);
this.productsContainer = this.database.container(productsContainerId);
this.ordersContainer = this.database.container(ordersContainerId);
}
async storeCart(cart: Cart) {
try {
const sqlQuerySpec: SqlQuerySpec = {
query: "SELECT TOP 1 * FROM c WHERE c.userName = @userName",
parameters: [
{
name: "@userName",
value: cart.userName,
},
],
};
const { resources: items } = await this.cartsContainer.items.query(sqlQuerySpec).fetchAll();
if (items.length > 0) {
const existingCart = items[0];
existingCart.items = cart.items;
this.cartsContainer.items.upsert(existingCart);
} else {
this.cartsContainer.items.upsert(cart);
}
return {
data: items,
statusCode: 200,
};
} catch (error) {
return {
error: "Error storing cart in Cosmos DB: " + error,
statusCode: 500,
};
}
}
async loadCart(userName: string) {
try {
const sqlQuerySpec: SqlQuerySpec = {
query: "SELECT TOP 1 * FROM c WHERE c.userName = @userName",
parameters: [
{
name: "@userName",
value: userName,
},
],
};
const { resources: items } = await this.cartsContainer.items.query(sqlQuerySpec).fetchAll();
return {
data: items,
statusCode: 200,
};
} catch (error) {
return {
error: "Error loading cart from Cosmos DB: " + error,
statusCode: 500,
};
}
}
async getProducts() {
const startTime = Date.now();
try {
const query = `SELECT c.id, c.type, c.brand, c.name, c.description, c.price FROM c`;
const sqlQuerySpec: SqlQuerySpec = {
query: query,
};
const { resources: items } = await this.productsContainer.items.query(sqlQuerySpec).fetchAll();
const duration = Date.now() - startTime;
console.debug(`[${sourceFile}] getProducts: ${duration} ms`);
return {
data: items,
statusCode: 200,
duration: duration,
};
} catch (error) {
return {
error: "Error fetching products from Cosmos DB: " + error,
statusCode: 500,
};
}
}
async createOrder(cart: Cart) {
const startTime = Date.now();
try {
const order = {
id: Date.now().toString(),
items: cart.items,
userName: cart.userName,
total: cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
status: "completed",
createdAt: new Date().toISOString(),
};
const { resource } = await this.ordersContainer.items.create(order);
const duration = Date.now() - startTime;
console.debug(`[${sourceFile}] createOrder: ${duration} ms`);
return {
data: resource,
statusCode: 200,
duration: duration,
};
} catch (error) {
return {
error: "Error creating order in Cosmos DB: " + error,
statusCode: 500,
};
}
}
}
// Create a singleton instance
export const cosmosDBService = new CosmosDBService();
```
--------------------------------------------------------------------------------
/nextjs/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';
const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-2 text-center sm:text-left',
className
)}
{...props}
/>
);
AlertDialogHeader.displayName = 'AlertDialogHeader';
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}
/>
);
AlertDialogFooter.displayName = 'AlertDialogFooter';
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold', className)}
{...props}
/>
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: 'outline' }),
'mt-2 sm:mt-0',
className
)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/toast.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import { cn } from '@/lib/utils';
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
className
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
{
variants: {
variant: {
default: 'border bg-background text-foreground',
destructive:
'destructive group border-destructive bg-destructive text-destructive-foreground',
},
},
defaultVariants: {
variant: 'default',
},
}
);
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
);
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
className
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
className
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn('text-sm font-semibold', className)}
{...props}
/>
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn('text-sm opacity-90', className)}
{...props}
/>
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/command.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Dialog, DialogContent } from '@/components/ui/dialog';
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
className
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
className
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn('-mx-1 h-px bg-border', className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
className
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
'ml-auto text-xs tracking-widest text-muted-foreground',
className
)}
{...props}
/>
);
};
CommandShortcut.displayName = 'CommandShortcut';
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from 'react';
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cva } from 'class-variance-authority';
import { ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
'relative z-10 flex max-w-max flex-1 items-center justify-center',
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
'group flex flex-1 list-none items-center justify-center space-x-1',
className
)}
{...props}
/>
));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50'
);
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), 'group', className)}
{...props}
>
{children}{' '}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ',
className
)}
{...props}
/>
));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn('absolute left-0 top-full flex justify-center')}>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]',
className
)}
ref={ref}
{...props}
/>
</div>
));
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName;
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName;
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};
```
--------------------------------------------------------------------------------
/mcp-server/src/cosmosdb-mcp-server.ts:
--------------------------------------------------------------------------------
```typescript
import { Container, CosmosClient, Database, SqlQuerySpec } from "@azure/cosmos";
import { initializeCosmosDB, validateEnvironmentVariables } from "./cosmosdb";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { getEmbeddingsAsync } from "./aoai";
import { z } from "zod";
export class CosmosDBMcpServer {
private server: McpServer;
private client: CosmosClient;
private database: Database;
private productsContainer: Container;
private ordersContainer: Container;
private sourceFile = "src/cosmosdb-mcp-server.ts";
constructor() {
this.server = new McpServer(
{
name: "cosmosdb-mcp-server",
version: "0.0.0",
},
{
capabilities: {
tools: {},
},
}
);
validateEnvironmentVariables();
// Initialize Cosmos DB connection
const { client, database, productsContainer, ordersContainer } = initializeCosmosDB();
this.client = client;
this.database = database;
this.productsContainer = productsContainer;
this.ordersContainer = ordersContainer;
this.initializeTools();
}
private async searchProducts(q: string) {
const startTime = Date.now();
try {
const embeddings = await getEmbeddingsAsync(q);
if (!embeddings) {
throw new Error("Failed to generate embeddings");
}
// console.debug(`[${this.sourceFile}::searchProducts] embeddings: ${embeddings}`);
const sqlQuerySpec: SqlQuerySpec = {
query: `
SELECT TOP 10
c.id, c.type, c.brand, c.name, c.description, c.price
FROM c
ORDER BY VectorDistance(c.embedding, @queryEmbedding)
`,
parameters: [{ name: "@queryEmbedding", value: embeddings }],
};
const { resources: items } = await this.productsContainer.items.query(sqlQuerySpec).fetchAll();
const duration = Date.now() - startTime;
console.debug(`[${this.sourceFile}::searchProducts] ${duration} ms`);
return {
data: items,
statusCode: 200,
duration: duration,
};
} catch (error) {
console.timeEnd("searchProducts");
return {
error: "Error fetching products from Cosmos DB: " + error,
statusCode: 500,
};
}
}
private async getOrders(email: string) {
const startTime = Date.now();
try {
const sqlQuerySpec: SqlQuerySpec = {
query: `
SELECT *
FROM c
WHERE c.email = @email
ORDER BY c.createdAt DESC
`,
parameters: [{ name: "@email", value: email }],
};
const { resources: items } = await this.ordersContainer.items.query(sqlQuerySpec).fetchAll();
const duration = Date.now() - startTime;
console.debug(`[${this.sourceFile}::getOrders] ${duration} ms`);
return {
data: items,
statusCode: 200,
duration: duration,
};
} catch (error) {
return {
error: "Error fetching orders from Cosmos DB: " + error,
statusCode: 500,
};
}
}
private initializeTools() {
this.server.tool(
"Weather",
"Get weather for a given location",
{
location: z.string(),
},
async (args, extra) => {
return {
content: [
{
type: "text",
text: `Here is the weather in: ${args.location}!`,
},
],
};
}
);
this.server.tool(
"searchProducts",
"Given a user query, search for matching products in the Azure Cosmos DB database",
{
query: z.string(),
},
async (args, extra) => {
try {
const { data, statusCode, duration } = await this.searchProducts(args.query);
console.debug(`[${this.sourceFile}::searchProducts] ${duration} ms`);
return {
content: [
{
type: "text",
text: JSON.stringify(data),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Error searching for products in Cosmos DB:" + error,
}),
},
],
isError: true,
};
}
}
);
this.server.tool(
"getOrders",
"Get all orders in the Azure Cosmos DB database for the selected email",
{
email: z.string(),
},
async (args, extra) => {
try {
const { data, statusCode, duration } = await this.getOrders(args.email);
console.debug(`[${this.sourceFile}::getOrders] ${duration} ms`);
return {
content: [
{
type: "text",
text: JSON.stringify(data),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Error getting orders for selected email: " + error,
}),
},
],
isError: true,
};
}
}
);
}
getServer() {
return this.server;
}
}
```
--------------------------------------------------------------------------------
/nextjs/app/cart/page.tsx:
--------------------------------------------------------------------------------
```typescript
// app/cart/page.tsx
'use client'
import { Minus, Plus, Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { EMAIL } from '@/models/constants'
import Image from 'next/image'
import { useCart } from '@/context/CartContext'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
export default function CartPage() {
const { items, removeItem, updateQuantity, clearCart } = useCart()
const router = useRouter()
const [imageData, setImageData] = useState<{ [key: string]: string }>({})
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
if (items.length === 0) {
return (
<div className='max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12'>
<h1 className='text-2xl font-bold mb-8'>Shopping Cart</h1>
<p>Your cart is empty</p>
</div>
)
}
const handlePlaceOrder = async () => {
try {
const orderId = uuidv4();
const order = {
id: orderId,
items: items,
total: total,
status: 'completed',
createdAt: new Date().toISOString(),
email: EMAIL
}
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(order)
})
if (response.ok) {
clearCart()
router.push(`/orders/${orderId}`)
} else {
throw new Error('Failed to place order')
}
} catch (error) {
console.error('Error placing order:', error)
}
}
return (
<div className='max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12'>
<h1 className='text-2xl font-bold mb-8'>Shopping Cart</h1>
<div className='grid grid-cols-1 lg:grid-cols-12 gap-8'>
<div className='lg:col-span-8'>
{items.map(item => (
<div key={item.id} className='flex gap-4 py-4 border-b'>
<Image
src={imageData[item.id] || '/placeholder-300x300.png'}
alt={item.name}
className='w-24 h-24 object-cover rounded'
width={96}
height={96}
unoptimized
onLoadingComplete={async () => {
const response = await fetch(
`/api/blob-url?blob=${item.id}.webp`,
{
cache: 'force-cache'
}
)
const data = await response.json()
setImageData(prevData => ({
...prevData,
[item.id]: data.url
}))
}}
/>
<div className='flex-1'>
<h3 className='font-medium'>{item.name}</h3>
<p className='text-gray-500'>${item.price}</p>
<div className='flex items-center gap-2 mt-2'>
<Button
variant='outline'
size='icon'
onClick={() => {
const newQuantity = Math.max(0, item.quantity - 1)
if (newQuantity === 0) {
removeItem(item.id)
} else {
updateQuantity(item.id, newQuantity)
}
}}
>
<Minus className='h-4 w-4' />
</Button>
<span className='w-8 text-center'>{item.quantity}</span>
<Button
variant='outline'
size='icon'
onClick={() => updateQuantity(item.id, item.quantity + 1)}
>
<Plus className='h-4 w-4' />
</Button>
<Button
variant='destructive'
size='icon'
onClick={() => {
console.log('Removing item:', item.id)
removeItem(item.id)
}}
>
<Trash2 className='h-4 w-4' />
</Button>
</div>
</div>
<div className='text-right'>
<p className='font-medium'>
${(item.price * item.quantity).toFixed(2)}
</p>
</div>
</div>
))}
</div>
<div className='lg:col-span-4'>
<div className='bg-gray-50 rounded-lg p-6'>
<h2 className='text-lg font-medium mb-4'>Order Summary</h2>
<div className='space-y-2'>
<div className='flex justify-between'>
<span>Subtotal</span>
<span>${total.toFixed(2)}</span>
</div>
<div className='flex justify-between'>
<span>Shipping</span>
<span>Free</span>
</div>
<div className='border-t pt-2 mt-2'>
<div className='flex justify-between font-medium'>
<span>Total</span>
<span>${total.toFixed(2)}</span>
</div>
</div>
</div>
<Button
className='w-full mt-6'
onClick={handlePlaceOrder}
>
Place order
</Button>
</div>
</div>
</div>
</div>
)
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/select.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { cn } from '@/lib/utils';
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from 'embla-carousel-react';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: 'horizontal' | 'vertical';
setApi?: (api: CarouselApi) => void;
};
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
} & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() {
const context = React.useContext(CarouselContext);
if (!context) {
throw new Error('useCarousel must be used within a <Carousel />');
}
return context;
}
const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = 'horizontal',
opts,
setApi,
plugins,
className,
children,
...props
},
ref
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === 'horizontal' ? 'x' : 'y',
},
plugins
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return;
}
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
const scrollPrev = React.useCallback(() => {
api?.scrollPrev();
}, [api]);
const scrollNext = React.useCallback(() => {
api?.scrollNext();
}, [api]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'ArrowLeft') {
event.preventDefault();
scrollPrev();
} else if (event.key === 'ArrowRight') {
event.preventDefault();
scrollNext();
}
},
[scrollPrev, scrollNext]
);
React.useEffect(() => {
if (!api || !setApi) {
return;
}
setApi(api);
}, [api, setApi]);
React.useEffect(() => {
if (!api) {
return;
}
onSelect(api);
api.on('reInit', onSelect);
api.on('select', onSelect);
return () => {
api?.off('select', onSelect);
};
}, [api, onSelect]);
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn('relative', className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
);
}
);
Carousel.displayName = 'Carousel';
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel();
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
'flex',
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
className
)}
{...props}
/>
</div>
);
});
CarouselContent.displayName = 'CarouselContent';
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel();
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
'min-w-0 shrink-0 grow-0 basis-full',
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
className
)}
{...props}
/>
);
});
CarouselItem.displayName = 'CarouselItem';
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
'absolute h-8 w-8 rounded-full',
orientation === 'horizontal'
? '-left-12 top-1/2 -translate-y-1/2'
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
);
});
CarouselPrevious.displayName = 'CarouselPrevious';
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
'absolute h-8 w-8 rounded-full',
orientation === 'horizontal'
? '-right-12 top-1/2 -translate-y-1/2'
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
);
});
CarouselNext.displayName = 'CarouselNext';
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
};
```
--------------------------------------------------------------------------------
/populate/program.cs:
--------------------------------------------------------------------------------
```csharp
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
internal class Program
{
private static string AzureOpenAIEndpoint;
private static string EmbeddingModel;
private static readonly HttpClient httpClient = new HttpClient();
private static async Task Main(string[] args)
{
// Build configuration
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
#region Get settings from appsettings.json and validate inputs
// Read AzureOpenAI settings from configuration
var azureOpenAISettings = configuration.GetSection("AzureOpenAI");
string AzureOpenAIUrl =
azureOpenAISettings["Endpoint"]
?? throw new ArgumentNullException("Azure OpenAI Endpoint cannot be null");
if (AzureOpenAIUrl == "https://<azure_openai_account>.openai.azure.com")
{
throw new ArgumentException(
"Azure OpenAI Endpoint must be configured with a valid value in appsettings.json"
);
}
string AzureOpenAIApiKey =
azureOpenAISettings["Key"]
?? throw new ArgumentNullException("Azure OpenAI Key cannot be null");
if (AzureOpenAIApiKey == "<azure_openai_key>")
{
throw new ArgumentException(
"Azure OpenAI Key must be configured with a valid value in appsettings.json"
);
}
string AzureOpenAIApiVersion =
azureOpenAISettings["ApiVersion"]
?? throw new ArgumentNullException("Azure OpenAI API Version cannot be null");
EmbeddingModel =
azureOpenAISettings["EmbeddingModel"]
?? throw new ArgumentNullException("Azure OpenAI Embedding model cannot be null");
// Read Cosmos DB settings from configuration
var cosmosDbSettings = configuration.GetSection("CosmosDb");
string endpoint =
cosmosDbSettings["Endpoint"]
?? throw new ArgumentNullException("Endpoint cannot be null");
if (endpoint == "https://<cosmosdb_account_name>.documents.azure.com:443/")
{
throw new ArgumentException(
"Cosmos DB AEndpoint must be configured with a valid value in appsettings.json"
);
}
string tenantId =
cosmosDbSettings["TenantId"]
?? throw new ArgumentNullException("TenantId cannot be null");
if (tenantId == "<tenant_id>")
{
throw new ArgumentException(
"TenantId must be configured with a valid value in appsettings.json"
);
}
string databaseName =
cosmosDbSettings["DatabaseName"]
?? throw new ArgumentNullException("DatabaseName cannot be null");
string containerName =
cosmosDbSettings["ProductsContainerName"]
?? throw new ArgumentNullException("ProductsContainerName cannot be null");
#endregion
// Set Azure OpenAI endpoint
AzureOpenAIEndpoint =
$"{AzureOpenAIUrl}/openai/deployments/{EmbeddingModel}/embeddings?api-version={AzureOpenAIApiVersion}";
Console.WriteLine(AzureOpenAIEndpoint);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
AzureOpenAIApiKey
);
// Create Cosmos DB client
var credentialOptions = new DefaultAzureCredentialOptions
{
TenantId = tenantId,
Diagnostics = { IsLoggingEnabled = true },
};
var credential = new DefaultAzureCredential(credentialOptions);
CosmosClientBuilder builder = new(endpoint, credential);
// Create Cosmos DB client
using CosmosClient client = builder.Build();
// Create database if it does not exist
Console.WriteLine($"Creating database {databaseName} if it does not exist");
Database database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
// Create container if it does not exist
Console.WriteLine($"Creating container {containerName} if it does not exist");
Container container = await database.CreateContainerIfNotExistsAsync(containerName, "/id");
// Read JSON file and deserialize to list of products
Console.WriteLine("Reading JSON file and deserializing to list of products");
string jsonFilePath = "catalog.json";
string jsonString = File.ReadAllText(jsonFilePath);
var products = JsonConvert.DeserializeObject<List<Product>>(jsonString) ?? [];
Stopwatch stopwatch = new();
stopwatch.Restart();
Console.WriteLine("Populating Cosmos DB with products");
foreach (var product in products)
{
dynamic p = new
{
id = product.id.ToString(),
type = product.type,
brand = product.brand,
name = product.name,
description = product.description,
price = product.price,
embedding = await GetEmbeddingsAsync(JsonConvert.SerializeObject(product)),
};
var response = await container.CreateItemAsync<dynamic>(p);
Console.WriteLine(product.name);
}
stopwatch.Stop();
Console.WriteLine($"Duration: {stopwatch.ElapsedMilliseconds} ms");
}
private static async Task<float[]> GetEmbeddingsAsync(string inputText)
{
var requestBody = new { model = EmbeddingModel, input = inputText };
var json = System.Text.Json.JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json")
{
CharSet = "utf-8",
};
var response = await httpClient.PostAsync(AzureOpenAIEndpoint, content);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
var responseJson = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(responseString);
return responseJson
.GetProperty("data")[0]
.GetProperty("embedding")
.EnumerateArray()
.Select(x => x.GetSingle())
.ToArray();
}
}
```
--------------------------------------------------------------------------------
/nextjs/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from '@/lib/utils';
const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
{...props}
/>
));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
));
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
'px-2 py-1.5 text-sm font-semibold text-foreground',
inset && 'pl-8',
className
)}
{...props}
/>
));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-border', className)}
{...props}
/>
));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
'ml-auto text-xs tracking-widest text-muted-foreground',
className
)}
{...props}
/>
);
};
ContextMenuShortcut.displayName = 'ContextMenuShortcut';
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};
```
--------------------------------------------------------------------------------
/nextjs/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
```typescript
'use client';
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from '@/lib/utils';
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && 'pl-8',
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
));
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
'px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className
)}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...props}
/>
);
};
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};
```