-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): initial code for workspace page [2024-10-31]
- Loading branch information
Showing
15 changed files
with
1,967 additions
and
97 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
export async function GET(request) { | ||
try { | ||
// Get the cookies from the request | ||
const cookies = request.headers.get('cookie'); | ||
if (!cookies) { | ||
throw new Error('No cookies present'); | ||
} | ||
|
||
// Parse cookies string to get sid value | ||
const sidCookie = cookies.split(';') | ||
.find(cookie => cookie.trim().startsWith('sid=')); | ||
|
||
if (!sidCookie) { | ||
throw new Error('Session ID cookie not found'); | ||
} | ||
|
||
// Extract the sid value | ||
const sidValue = sidCookie.split('=')[1].trim(); | ||
|
||
// Make the profile request with the sid as bearer token | ||
const response = await fetch('http://localhost:3001/profile', { | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Authorization': `Bearer ${sidValue}` | ||
} | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error('Profile retrieval failed'); | ||
} | ||
|
||
const data = await response.json(); | ||
|
||
return new Response(JSON.stringify({ success: true, data }), { | ||
status: 200, | ||
}); | ||
} catch (error) { | ||
return new Response( | ||
JSON.stringify({ | ||
success: false, | ||
error: 'Profile retrieval failed.' | ||
}), | ||
{ status: 401 } | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
'use client' | ||
import React, { useEffect, useState } from 'react'; | ||
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; | ||
import { AppSidebar } from "./workspace-sidebar"; | ||
import { Menu } from "lucide-react"; | ||
|
||
export default function Layout({ children }: { children: React.ReactNode }) { | ||
const [profileData, setProfileData] = useState({ | ||
first_name: '', | ||
email: '[email protected]', // fallback default | ||
avatar_url: '/path/to/avatar.jpg' // fallback default | ||
}); | ||
const [isLoading, setIsLoading] = useState(true); | ||
const [error, setError] = useState<string | null>(null); | ||
|
||
useEffect(() => { | ||
const fetchProfile = async () => { | ||
try { | ||
setIsLoading(true); | ||
const response = await fetch('/api/profile'); | ||
|
||
if (!response.ok) { | ||
throw new Error('Failed to fetch profile'); | ||
} | ||
|
||
const data = await response.json(); | ||
setProfileData({ | ||
first_name: data.first_name, | ||
email: data.email || profileData.email, | ||
avatar_url: data.avatar_url || profileData.avatar_url | ||
}); | ||
} catch (error) { | ||
console.error('Error fetching profile:', error); | ||
setError(error instanceof Error ? error.message : 'Failed to load profile'); | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
}; | ||
|
||
fetchProfile(); | ||
}, []); // Empty dependency array means this runs once on mount | ||
|
||
return ( | ||
<SidebarProvider> | ||
<div className="flex h-screen"> | ||
<AppSidebar | ||
userName={profileData.first_name} | ||
userEmail={profileData.email} | ||
avatarUrl={profileData.avatar_url} | ||
/> | ||
<main className="flex-1 px-4 overflow-auto bg-gray-50"> | ||
<div className="p-4"> | ||
<SidebarTrigger className="lg:hidden"> | ||
<Menu className="h-5 w-5" /> | ||
</SidebarTrigger> | ||
{isLoading ? ( | ||
<div>Loading profile...</div> | ||
) : error ? ( | ||
<div>Error: {error}</div> | ||
) : ( | ||
children | ||
)} | ||
</div> | ||
</main> | ||
</div> | ||
</SidebarProvider> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,62 @@ | ||
import Image from "next/image"; | ||
import React from 'react'; | ||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'; | ||
|
||
export default function Home() { | ||
return ( | ||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> | ||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> | ||
<Image | ||
className="dark:invert" | ||
src="/next.svg" | ||
alt="Next.js logo" | ||
width={180} | ||
height={38} | ||
priority | ||
/> | ||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> | ||
<li className="mb-2"> | ||
Get started by editing{" "} | ||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> | ||
src/app/page.tsx | ||
</code> | ||
. | ||
</li> | ||
<li>Save and see your changes instantly.</li> | ||
</ol> | ||
export default function Workspace() { | ||
const projects = [ | ||
{ | ||
title: "Downtown District Mapping", | ||
description: "Detailed street-level mapping of the central business district", | ||
status: "In Progress", | ||
}, | ||
{ | ||
title: "Parks & Recreation Zones", | ||
description: "Comprehensive mapping of public recreational areas and green spaces", | ||
status: "Planning", | ||
}, | ||
{ | ||
title: "Historic Districts Survey", | ||
description: "Documentation and mapping of heritage sites and landmarks", | ||
status: "Not Started", | ||
} | ||
]; | ||
|
||
<div className="flex gap-4 items-center flex-col sm:flex-row"> | ||
<a | ||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5" | ||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
className="dark:invert" | ||
src="/vercel.svg" | ||
alt="Vercel logomark" | ||
width={20} | ||
height={20} | ||
/> | ||
Deploy now | ||
</a> | ||
<a | ||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" | ||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
Read our docs | ||
</a> | ||
</div> | ||
</main> | ||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center"> | ||
<a | ||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
aria-hidden | ||
src="/file.svg" | ||
alt="File icon" | ||
width={16} | ||
height={16} | ||
/> | ||
Learn | ||
</a> | ||
<a | ||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
aria-hidden | ||
src="/window.svg" | ||
alt="Window icon" | ||
width={16} | ||
height={16} | ||
/> | ||
Examples | ||
</a> | ||
<a | ||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
aria-hidden | ||
src="/globe.svg" | ||
alt="Globe icon" | ||
width={16} | ||
height={16} | ||
/> | ||
Go to nextjs.org → | ||
</a> | ||
</footer> | ||
return ( | ||
<div className="mt-4"> | ||
<div className="mb-8"> | ||
<h1 className="text-4xl font-bold text-slate-900 mb-2"> | ||
Map Projects | ||
</h1> | ||
<p className="text-slate-600 text-lg font-medium"> | ||
Geographic information systems and mapping initiatives | ||
</p> | ||
</div> | ||
|
||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> | ||
{projects.map((project, index) => ( | ||
<Card key={index} className="group hover:shadow-lg transition-all duration-300"> | ||
<div className="aspect-video w-full relative overflow-hidden"> | ||
<img | ||
src="/api/placeholder/400/225" | ||
alt={`Map preview for ${project.title}`} | ||
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-300" | ||
/> | ||
<div className="absolute top-2 right-2"> | ||
<span className="bg-white/90 backdrop-blur-sm text-slate-700 px-3 py-1 rounded-full text-sm font-medium"> | ||
{project.status} | ||
</span> | ||
</div> | ||
</div> | ||
<CardHeader className="space-y-2"> | ||
<CardTitle className="text-xl font-bold text-slate-900 line-clamp-1"> | ||
{project.title} | ||
</CardTitle> | ||
<CardDescription className="text-slate-600 font-medium line-clamp-2"> | ||
{project.description} | ||
</CardDescription> | ||
</CardHeader> | ||
</Card> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React from 'react'; | ||
import { Sidebar, SidebarContent } from "@/components/ui/sidebar"; | ||
import { Database, Map } from "lucide-react"; | ||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; | ||
|
||
interface AppSidebarProps { | ||
userName?: string; | ||
userEmail?: string; | ||
avatarUrl?: string; | ||
} | ||
|
||
const navItems = [ | ||
{ icon: Map, label: 'Projects', href: '/workspace' }, | ||
{ icon: Database, label: 'Connections', href: '/workspace' }, | ||
]; | ||
|
||
export function AppSidebar({ | ||
userName = "John Doe", | ||
userEmail = "[email protected]", | ||
avatarUrl = "/api/placeholder/32/32" | ||
}: AppSidebarProps) { | ||
// Get first letter of name for avatar fallback | ||
const avatarFallback = userName.charAt(0).toUpperCase(); | ||
|
||
return ( | ||
<Sidebar className=""> | ||
<SidebarContent> | ||
<div className="flex h-full flex-col"> | ||
{/* Logo */} | ||
<div className="px-4 py-6"> | ||
<h1 className="text-xl font-bold">Workspace</h1> | ||
</div> | ||
|
||
{/* Navigation */} | ||
<nav className="flex-1 space-y-1 px-2"> | ||
{navItems.map((item) => ( | ||
<a | ||
key={item.label} | ||
href={item.href} | ||
className="flex items-center rounded-lg px-3 py-2 text-sm font-medium hover:bg-gray-100" | ||
> | ||
<item.icon className="mr-3 h-5 w-5" /> | ||
{item.label} | ||
</a> | ||
))} | ||
</nav> | ||
|
||
{/* User Profile */} | ||
<div className="border-t p-4"> | ||
<div className="flex items-center gap-3"> | ||
<Avatar> | ||
<AvatarImage src={avatarUrl} alt={userName} /> | ||
<AvatarFallback>{avatarFallback}</AvatarFallback> | ||
</Avatar> | ||
<div> | ||
<p className="text-sm font-medium">{userName}</p> | ||
<p className="text-xs text-gray-500">{userEmail}</p> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</SidebarContent> | ||
</Sidebar> | ||
); | ||
} |
Oops, something went wrong.