Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4e77e11
add .env and github action for deploy
UchihaIthachi Jun 12, 2025
4e5c7a5
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
deac74f
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
0e1a702
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
04ace3c
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
1f9bc6b
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
f5edede
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
78c776d
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
d7fd9c5
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
8d68db8
add .vercilignore
UchihaIthachi Jun 12, 2025
1aff86d
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
d5a613f
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
e332f63
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
328057a
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
f30148b
Fix: Resolve type error in PlaceDetailPage
google-labs-jules[bot] Jun 12, 2025
81a4021
Merge pull request #3 from TravelMateAI/fix/place-details-type-error
UchihaIthachi Jun 12, 2025
06a2a27
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
9c309ee
update vercle.json
UchihaIthachi Jun 12, 2025
c7de1b9
Merge branch 'deploy' of https://github.com/TravelMateAI/GoTogether-w…
UchihaIthachi Jun 12, 2025
02fac0c
Update vercel.json
UchihaIthachi Jun 12, 2025
6303303
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
f66ca8e
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
e5a232c
Update vercel.json
UchihaIthachi Jun 12, 2025
9ec103f
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
eab46c7
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
ce9c7e3
Update vercel.json
UchihaIthachi Jun 12, 2025
33932e1
Update deploy-vercel.yml
UchihaIthachi Jun 12, 2025
9cf5bef
Update vercel.json
UchihaIthachi Jun 12, 2025
c2854c4
update
UchihaIthachi Jun 12, 2025
3a35fea
update
UchihaIthachi Jun 12, 2025
7bc9119
update
UchihaIthachi Jun 12, 2025
7197dc9
update
UchihaIthachi Jun 12, 2025
41f2fae
update
UchihaIthachi Jun 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/deploy-vercel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Deploy to Vercel

on:
push:
branches:
- deploy

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "20.16.0"

- name: Install Vercel CLI
run: npm install --global vercel@latest

- name: Print environment info
run: |
node -v
npm -v
vercel --version

- name: Install dependencies with legacy peer deps
run: npm install --legacy-peer-deps

- name: Build with Next.js
run: npm run build

- name: Deploy to Vercel
run: vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ yarn-error.log*
.env*

# vercel
.vercel
# .vercel

# typescript
*.tsbuildinfo
Expand Down
11 changes: 11 additions & 0 deletions .vercel/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
> Why do I have a folder named ".vercel" in my project?
The ".vercel" folder is created when you link a directory to a Vercel project.

> What does the "project.json" file contain?
The "project.json" file contains:
- The ID of the Vercel project that you linked ("projectId")
- The ID of the user or team your Vercel project is owned by ("orgId")

> Should I commit the ".vercel" folder?
No, you should not share the ".vercel" folder with anyone.
Upon creation, it will be automatically added to your ".gitignore" file.
15 changes: 15 additions & 0 deletions .vercel/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"projectId": "prj_te8nIZ8DyvHEtnS5zjg84EWRNhNo",
"orgId": "team_Hdro0YYN8FfVYjQGRSNOr3xB",
"settings": {
"createdAt": 1749712056965,
"framework": "nextjs",
"devCommand": null,
"installCommand": null,
"buildCommand": null,
"outputDirectory": null,
"rootDirectory": null,
"directoryListing": false,
"nodeVersion": "20.x"
}
}
3 changes: 3 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Add to your .vercelignore

node_modules
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,20 @@ Ensure you have the following installed:
npm install prisma --save-dev
```

2. **Set up your database connection:**
2. **Set up your environment variables:**

- Create a `.env` file in the root of your project with the following content:
```
DATABASE_URL="postgresql://user:password@localhost:5432/gotogether"
- This project uses environment variables for configuration. A template file `.env.example` is provided in the root directory.
- Copy this file to a new file named `.env`:
```bash
cp .env.example .env
```
- **Important**: Open the `.env` file and fill in the required values for your local development environment. This includes:
- `DATABASE_URL`: Your PostgreSQL connection string. For local development, it might look like `postgresql://YOUR_USER:YOUR_PASSWORD@localhost:5432/gotogether?schema=public`. If using Docker, the hostname might be `db` (e.g., `postgresql://user:password@db:5432/gotogether`).
- `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`: Your Google OAuth credentials.
- `UPLOADTHING_SECRET` and `NEXT_PUBLIC_UPLOADTHING_APP_ID`: Your UploadThing credentials.
- URLs for backend services (`NEXT_PUBLIC_BACKEND_URL`, `NEXT_PUBLIC_GO_BACKEND_URL`) if they differ from the defaults.
- Keycloak URLs if you are using a custom Keycloak instance.
- The `.env` file is already listed in `.gitignore` and should not be committed to your repository.

3. **Run Prisma migrations:**

Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ export async function logout() {
path: "/api/auth/refresh",
maxAge: 0,
});
const keycloakLogoutUrl = `http://localhost:8084/realms/kong/protocol/openid-connect/logout?client_id=kong-oidc&post_logout_redirect_uri=http://localhost:3000/login`;
const keycloakLogoutUrl = `${process.env.NEXT_PUBLIC_KEYCLOAK_LOGOUT_URL || "http://localhost:8084/realms/kong/protocol/openid-connect/logout"}?client_id=kong-oidc&post_logout_redirect_uri=${process.env.NEXT_PUBLIC_KEYCLOAK_REDIRECT_URI || "http://localhost:3000/login"}`;
return redirect(keycloakLogoutUrl);
}
5 changes: 4 additions & 1 deletion src/app/(auth)/login/GoogleSignInButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export default function GoogleSignInButton() {
asChild
>
<a
href="http://localhost:8080/oauth2/authorization/google"
href={
process.env.NEXT_PUBLIC_GOOGLE_AUTH_URL ||
"http://localhost:8080/oauth2/authorization/google"
}
className="flex w-full items-center gap-2"
>
<GoogleIcon />
Expand Down
33 changes: 18 additions & 15 deletions src/app/(auth)/signup/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,29 @@ import { isRedirectError } from "next/dist/client/components/redirect";
import { redirect } from "next/navigation";

export async function signUp(
credentials: SignUpValues
credentials: SignUpValues,
): Promise<{ error: string } | void> {
try {
const { username, email, password, firstName, lastName } = signUpSchema.parse(credentials);
const { username, email, password, firstName, lastName } =
signUpSchema.parse(credentials);

// 👇 New: Call your Spring Boot backend API
const res = await fetch("http://localhost:8080/api/users/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
const res = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080"}/api/users/register`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username,
email,
password,
firstName, // <-- Optional, you can ask these fields from user if needed
lastName,
}),
},
body: JSON.stringify({
username,
email,
password,
firstName, // <-- Optional, you can ask these fields from user if needed
lastName,
}),
});
);

if (!res.ok) {
const err = await res.text();
Expand All @@ -32,7 +36,6 @@ export async function signUp(
}

return redirect("/login");

} catch (error) {
if (isRedirectError(error)) throw error;
console.error(error);
Expand Down
131 changes: 71 additions & 60 deletions src/app/(main)/place/[placeId]/actions.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,81 @@
"use server";

import { goKyInstance } from "../../../../../src/lib/ky"; // Adjusted path to src/lib/ky
import type { PlaceDetailsResponse, PlaceDetails } from "@/types/location-types"; // Verify path
import { goKyInstance } from "../../../../../src/lib/ky"; // Adjusted path to src/lib/ky
import type {
PlaceDetailsResponse,
PlaceDetails,
} from "@/types/location-types"; // Verify path

interface GetPlaceDetailsResult {
success: boolean;
data?: PlaceDetails; // This is the 'result' object from PlaceDetailsResponse
error?: string;
status?: string; // To pass along Google's status like "NOT_FOUND", "INVALID_REQUEST"
}
interface GetPlaceDetailsResult {
success: boolean;
data?: PlaceDetails; // This is the 'result' object from PlaceDetailsResponse
error?: string;
status?: string; // To pass along Google's status like "NOT_FOUND", "INVALID_REQUEST"
}

export async function getPlaceDetailsByIdAction(
placeId: string,
): Promise<GetPlaceDetailsResult> {
if (!placeId || placeId.trim() === "") {
return { success: false, error: "Place ID is required.", status: "INVALID_REQUEST_CLIENT" };
}
export async function getPlaceDetailsByIdAction(
placeId: string,
): Promise<GetPlaceDetailsResult> {
if (!placeId || placeId.trim() === "") {
return {
success: false,
error: "Place ID is required.",
status: "INVALID_REQUEST_CLIENT",
};
}

try {
// The API path is /maps/place/:place_id
// goKyInstance will append this to its prefixUrl: http://localhost:8000
// So the final URL will be http://localhost:8000/maps/place/{placeId}
const response = await goKyInstance.get(`maps/place/${placeId}`).json<PlaceDetailsResponse>();
try {
// The API path is /maps/place/:place_id
// goKyInstance will append this to its prefixUrl (NEXT_PUBLIC_GO_BACKEND_URL, defaults to http://localhost:8083)
// So the final URL will be <prefixUrl>/maps/place/{placeId}
const response = await goKyInstance
.get(`maps/place/${placeId}`)
.json<PlaceDetailsResponse>();

// According to the API documentation for /maps/place/:place_id:
// - Success (200 OK) contains the PlaceDetailsResponse structure.
// - The 'status' field within the response body indicates Google's processing status.
// - Errors like 400, 403, 404, 429 are also possible from our backend,
// but ky throws HTTPError for non-2xx responses, which is caught below.
// If the backend wraps Google's error status (like NOT_FOUND) in a 200 OK response from *our* service,
// then we check response.status here.
// According to the API documentation for /maps/place/:place_id:
// - Success (200 OK) contains the PlaceDetailsResponse structure.
// - The 'status' field within the response body indicates Google's processing status.
// - Errors like 400, 403, 404, 429 are also possible from our backend,
// but ky throws HTTPError for non-2xx responses, which is caught below.
// If the backend wraps Google's error status (like NOT_FOUND) in a 200 OK response from *our* service,
// then we check response.status here.

if (response.status === "OK") {
return { success: true, data: response.result, status: response.status };
} else {
// Handles cases like "ZERO_RESULTS", "NOT_FOUND", "INVALID_REQUEST" etc.
// returned by Google but wrapped in a 200 OK from our service.
console.error(
`API returned non-OK status for place ${placeId}: ${response.status} - ${response.error_message || ""}`,
);
return {
success: false,
error: response.error_message || `API Error: ${response.status}`,
status: response.status,
};
}
} catch (error: any) {
console.error(`Error fetching details for place ${placeId}:`, error);
let errorMessage = "Failed to fetch place details.";
let errorStatus = "UNKNOWN_ERROR_CLIENT";
if (response.status === "OK") {
return { success: true, data: response.result, status: response.status };
} else {
// Handles cases like "ZERO_RESULTS", "NOT_FOUND", "INVALID_REQUEST" etc.
// returned by Google but wrapped in a 200 OK from our service.
console.error(
`API returned non-OK status for place ${placeId}: ${response.status} - ${response.error_message || ""}`,
);
return {
success: false,
error: response.error_message || `API Error: ${response.status}`,
status: response.status,
};
}
} catch (error: any) {
console.error(`Error fetching details for place ${placeId}:`, error);
let errorMessage = "Failed to fetch place details.";
let errorStatus = "UNKNOWN_ERROR_CLIENT";

if (error.name === 'HTTPError') { // Ky specific HTTP error
try {
const errorResponse = await error.response.json();
// Assuming error response from our Go service might look like:
// { "error": "message", "details": "...", "status": "GOOGLE_STATUS_CODE" }
// or directly Google's PlaceDetailsResponse structure with a non-OK status.
errorMessage = errorResponse.error_message || errorResponse.error || error.message;
errorStatus = errorResponse.status || `HTTP_${error.response.status}`;
} catch (e) {
errorMessage = `API Error: ${error.response.status} - ${error.message}`;
errorStatus = `HTTP_${error.response.status}`;
}
} else if (error instanceof Error) {
errorMessage = error.message;
}
return { success: false, error: errorMessage, status: errorStatus };
if (error.name === "HTTPError") {
// Ky specific HTTP error
try {
const errorResponse = await error.response.json();
// Assuming error response from our Go service might look like:
// { "error": "message", "details": "...", "status": "GOOGLE_STATUS_CODE" }
// or directly Google's PlaceDetailsResponse structure with a non-OK status.
errorMessage =
errorResponse.error_message || errorResponse.error || error.message;
errorStatus = errorResponse.status || `HTTP_${error.response.status}`;
} catch (e) {
errorMessage = `API Error: ${error.response.status} - ${error.message}`;
errorStatus = `HTTP_${error.response.status}`;
}
} else if (error instanceof Error) {
errorMessage = error.message;
}
return { success: false, error: errorMessage, status: errorStatus };
}
}
5 changes: 4 additions & 1 deletion src/app/(main)/place/[placeId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,10 @@ export default function PlaceDetailPage({ params }: PlaceDetailPageProps) {
</h2>
<HorizontalScrollBar
title="" // Title is handled by the h2 above
cardData={relatedPlaces} // relatedPlaces are PlaceDetails[], compatible with LocationDetail[]
cardData={relatedPlaces.map(place => ({
...place,
vicinity: place.vicinity || "",
}))} // relatedPlaces are PlaceDetails[], compatible with LocationDetail[]
images={relatedPlaces.map(p => p.photo_urls?.[0] || DEFAULT_IMAGE_URL)}
scrollButton={{ route: "" as any, loading: false }} // Effectively hides "See all"
handleNavigation={() => {}} // Dummy function as "See all" is hidden
Expand Down
Loading