feat: migrate landing page from Next.js to Astro
Replaces the heavy Next.js SSR app with Astro static site: - Zero JS shipped for static pages (hero, community, CTA, footer) - Server-side data fetching in Astro frontmatter (events, hackathon, testimonials) - Only 4 React islands for interactive parts (mobile menu, roadmap voting, testimonials home, feature request) - Google Fonts via <link> instead of next/font - Nix build changed from Node.js systemd service to static nginx (mkStaticAppModule) - npmDepsHash needs update (set to fake hash, CI will provide correct one) Performance gains: - No Node.js server process needed (was using ~10MB RAM) - No Next.js runtime JS (~200KB+ saved) - No framer-motion bundle (~130KB saved) - Static HTML served directly by nginx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0ade3c3e50
commit
95bf09c2fa
@@ -0,0 +1,13 @@
|
||||
---
|
||||
---
|
||||
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="refresh" content="0;url=https://forms.gle/fNzQWSdqXoyFsf8V6" />
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to registration form...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
import Public from '../layouts/Public.astro';
|
||||
|
||||
interface ApiEvent {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
detail_link: string;
|
||||
price: number;
|
||||
is_online: boolean;
|
||||
is_deleted: boolean;
|
||||
location: string;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
let events: ApiEvent[] = [];
|
||||
try {
|
||||
const res = await fetch('https://api.imphnen.dev/v1/landing/cms/events');
|
||||
const json = await res.json();
|
||||
events = (json.data || []).filter((e: ApiEvent) => !e.is_deleted);
|
||||
} catch {
|
||||
events = [];
|
||||
}
|
||||
|
||||
const sortedEvents = [...events].sort(
|
||||
(a, b) => new Date(b.start_date).getTime() - new Date(a.start_date).getTime()
|
||||
);
|
||||
|
||||
function formatDate(dateString: string) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
function formatTime(dateString: string) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Jakarta',
|
||||
});
|
||||
}
|
||||
|
||||
function getEventStatus(endDate: string) {
|
||||
const now = new Date();
|
||||
const end = new Date(endDate);
|
||||
return end > now ? 'upcoming' : 'past';
|
||||
}
|
||||
---
|
||||
|
||||
<Public title="Events - IMPHNEN">
|
||||
<section class="min-h-screen bg-background container py-10">
|
||||
{sortedEvents.length === 0 ? (
|
||||
<p class="text-center text-muted-foreground">
|
||||
Belum ada event tersedia.
|
||||
</p>
|
||||
) : (
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{/* Featured event */}
|
||||
<div class="col-span-full">
|
||||
<div class="rounded-xl shadow-sm hover:shadow-md transition-shadow duration-200 overflow-hidden bg-card">
|
||||
<div class="p-8 flex flex-col">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="bg-primary/20 text-primary text-xs px-2.5 py-1 rounded-full">
|
||||
Event Terbaru
|
||||
</span>
|
||||
<span
|
||||
class:list={[
|
||||
'px-2 py-1 rounded-full text-xs',
|
||||
getEventStatus(sortedEvents[0].end_date) === 'upcoming'
|
||||
? 'bg-primary/20 text-primary'
|
||||
: 'bg-muted text-muted-foreground',
|
||||
]}
|
||||
>
|
||||
{getEventStatus(sortedEvents[0].end_date) === 'upcoming' ? 'Upcoming' : 'Selesai'}
|
||||
</span>
|
||||
<span
|
||||
class:list={[
|
||||
'px-2 py-1 rounded-full text-xs',
|
||||
sortedEvents[0].is_online
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-green-100 text-green-700',
|
||||
]}
|
||||
>
|
||||
{sortedEvents[0].is_online ? 'Online' : 'Onsite'}
|
||||
</span>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold mb-4 text-foreground">
|
||||
{sortedEvents[0].name}
|
||||
</h2>
|
||||
<div class="space-y-3 mb-6 text-sm text-muted-foreground">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
<span>{formatDate(sortedEvents[0].start_date)}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<span>
|
||||
{formatTime(sortedEvents[0].start_date)} - {formatTime(sortedEvents[0].end_date)} WIB
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||
<span>
|
||||
{sortedEvents[0].is_online ? 'Online' : sortedEvents[0].location}
|
||||
</span>
|
||||
</div>
|
||||
{sortedEvents[0].price > 0 && (
|
||||
<div class="mt-1 font-medium">
|
||||
Rp {sortedEvents[0].price.toLocaleString('id-ID')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p class="text-muted-foreground mb-6 line-clamp-4">
|
||||
{sortedEvents[0].description}
|
||||
</p>
|
||||
<a
|
||||
href={sortedEvents[0].detail_link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center justify-center rounded-lg border border-input bg-background px-6 py-3 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors mt-auto w-full md:w-fit"
|
||||
>
|
||||
Lihat Detail
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Other events */}
|
||||
{sortedEvents.slice(1).map((event) => (
|
||||
<div class="rounded-xl shadow-sm hover:shadow-md transition-shadow duration-200 bg-card">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<span
|
||||
class:list={[
|
||||
'px-2 py-1 rounded-full text-xs',
|
||||
getEventStatus(event.end_date) === 'upcoming'
|
||||
? 'bg-primary/20 text-primary'
|
||||
: 'bg-muted text-muted-foreground',
|
||||
]}
|
||||
>
|
||||
{getEventStatus(event.end_date) === 'upcoming' ? 'Upcoming' : 'Selesai'}
|
||||
</span>
|
||||
<span
|
||||
class:list={[
|
||||
'px-2 py-1 rounded-full text-xs',
|
||||
event.is_online
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-green-100 text-green-700',
|
||||
]}
|
||||
>
|
||||
{event.is_online ? 'Online' : 'Onsite'}
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-3 text-foreground">
|
||||
{event.name}
|
||||
</h3>
|
||||
<div class="space-y-2 mb-4 text-sm text-muted-foreground">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
<span>{formatDate(event.start_date)}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||
<span>{event.is_online ? 'Online' : event.location}</span>
|
||||
</div>
|
||||
{event.price > 0 && (
|
||||
<div class="font-medium">
|
||||
Rp {event.price.toLocaleString('id-ID')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p class="text-muted-foreground text-sm mb-4 line-clamp-3">
|
||||
{event.description}
|
||||
</p>
|
||||
<a
|
||||
href={event.detail_link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center justify-center rounded-lg border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors w-full"
|
||||
>
|
||||
Lihat Detail
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</Public>
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
import Public from '../layouts/Public.astro';
|
||||
|
||||
interface TeamItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
city: string;
|
||||
logo: string | null;
|
||||
banner: string | null;
|
||||
}
|
||||
|
||||
interface WinnerItem {
|
||||
id: string;
|
||||
team_id: string;
|
||||
team_name: string;
|
||||
rank: number;
|
||||
prize: string | null;
|
||||
}
|
||||
|
||||
let teams: TeamItem[] = [];
|
||||
let winners: WinnerItem[] = [];
|
||||
|
||||
try {
|
||||
const [teamsRes, winnersRes] = await Promise.all([
|
||||
fetch('https://api.imphnen.dev/v1/hackathon/teams/browse?per_page=20'),
|
||||
fetch('https://api.imphnen.dev/v1/hackathon/winners'),
|
||||
]);
|
||||
const teamsJson = await teamsRes.json();
|
||||
const winnersJson = await winnersRes.json();
|
||||
teams = teamsJson.data?.data || [];
|
||||
winners = winnersJson.data || [];
|
||||
} catch {
|
||||
teams = [];
|
||||
winners = [];
|
||||
}
|
||||
|
||||
function getWinnerRank(teamId: string): number | null {
|
||||
const w = winners.find((x) => x.team_id === teamId);
|
||||
return w ? w.rank : null;
|
||||
}
|
||||
|
||||
const sortedWinners = [...winners].sort((a, b) => a.rank - b.rank);
|
||||
---
|
||||
|
||||
<Public title="Hackathon - IMPHNEN">
|
||||
<section class="min-h-screen bg-background container py-10">
|
||||
{teams.length === 0 ? (
|
||||
<div class="flex items-center justify-center min-h-[50vh]">
|
||||
<div class="text-center">
|
||||
<p class="text-muted-foreground text-lg mb-2">No hackathon teams yet.</p>
|
||||
<a
|
||||
href="https://hackathon.imphnen.dev"
|
||||
class="inline-flex items-center justify-center rounded-lg bg-primary text-primary-foreground px-6 py-3 text-sm font-medium hover:bg-primary/90 transition-colors mt-4"
|
||||
>
|
||||
Join Hackathon
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold mb-3 text-foreground">IMPHNEN Hackathon</h1>
|
||||
<p class="text-muted-foreground text-lg max-w-2xl mx-auto">
|
||||
Tim-tim yang berpartisipasi dalam hackathon IMPHNEN
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{sortedWinners.length > 0 && (
|
||||
<div class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6 text-foreground flex items-center gap-2">
|
||||
<svg class="w-6 h-6 text-amber-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3l3.057-3L12 3.057 15.943 0 19 3l-3 7h-8L5 3zM12 14l-3-3h6l-3 3zm-5 4h10l1 3H6l1-3z" /></svg>
|
||||
Pemenang
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{sortedWinners.map((w) => (
|
||||
<div class="bg-amber-50 border-2 border-amber-200 rounded-xl p-6 text-center">
|
||||
<div class="text-4xl mb-2">
|
||||
{w.rank === 1 ? '\uD83E\uDD47' : w.rank === 2 ? '\uD83E\uDD48' : '\uD83E\uDD49'}
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-gray-900">{w.team_name}</h3>
|
||||
<p class="text-sm text-amber-700 mt-1">Juara {w.rank}</p>
|
||||
{w.prize && <p class="text-xs text-amber-600 mt-1">{w.prize}</p>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h2 class="text-2xl font-bold mb-6 text-foreground">Semua Tim</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{teams.map((team) => {
|
||||
const rank = getWinnerRank(team.id);
|
||||
return (
|
||||
<div
|
||||
class:list={[
|
||||
'rounded-xl shadow-sm hover:shadow-lg transition-all duration-300 bg-card group overflow-hidden',
|
||||
rank ? 'ring-2 ring-amber-300' : '',
|
||||
]}
|
||||
>
|
||||
{team.banner ? (
|
||||
<div class="h-40 bg-muted relative overflow-hidden">
|
||||
<img
|
||||
src={team.banner}
|
||||
alt={team.name}
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div class="h-40 bg-gradient-to-br from-primary-100 to-primary-200 flex items-center justify-center">
|
||||
<svg class="w-12 h-12 text-primary-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg>
|
||||
</div>
|
||||
)}
|
||||
<div class="p-6">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-foreground">{team.name}</h3>
|
||||
{rank && (
|
||||
<span class="text-xs bg-amber-100 text-amber-700 px-2 py-1 rounded-full font-medium">
|
||||
Juara {rank}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{team.city && (
|
||||
<p class="text-xs text-muted-foreground mb-2">{team.city}</p>
|
||||
)}
|
||||
{team.description && (
|
||||
<p class="text-muted-foreground text-sm mb-4 line-clamp-3">{team.description}</p>
|
||||
)}
|
||||
<a
|
||||
href={`https://hackathon.imphnen.dev/teams/${team.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center justify-center rounded-lg border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors w-full"
|
||||
>
|
||||
Lihat Tim
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
</Public>
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
import Public from '../layouts/Public.astro';
|
||||
import HeroSection from '../components/HeroSection.astro';
|
||||
import CommunitySection from '../components/CommunitySection.astro';
|
||||
import TestimonialSection from '../components/TestimonialSection';
|
||||
import CTASection from '../components/CTASection.astro';
|
||||
---
|
||||
|
||||
<Public>
|
||||
<HeroSection />
|
||||
<CommunitySection />
|
||||
<TestimonialSection client:visible />
|
||||
<CTASection />
|
||||
</Public>
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
import Public from '../layouts/Public.astro';
|
||||
import RoadmapVote from '../components/RoadmapVote';
|
||||
import RequestFeature from '../components/RequestFeature';
|
||||
---
|
||||
|
||||
<Public title="Roadmap - IMPHNEN">
|
||||
<div class="pt-4 md:pt-6 pb-56 bg-gray-50 min-h-screen mx-4 lg:mx-0">
|
||||
<!-- Feature Request CTA -->
|
||||
<div class="bg-primary-500 rounded-xl px-4 py-8 md:p-8 mb-8 text-center mx-auto max-w-7xl">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<h1 class="text-2xl md:text-3xl font-bold text-white mb-3">
|
||||
Punya Ide Bagus untuk IMPHNEN?
|
||||
</h1>
|
||||
<p class="text-white/90 mb-6 text-lg text-balance">
|
||||
Bagikan ide fitur yang kamu inginkan dan vote untuk membuatnya nyata!
|
||||
</p>
|
||||
<RequestFeature client:idle />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RoadmapVote client:visible />
|
||||
</div>
|
||||
</Public>
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
import Public from '../layouts/Public.astro';
|
||||
---
|
||||
|
||||
<Public title="Teams - IMPHNEN">
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<p class="text-muted-foreground">Coming soon.</p>
|
||||
</div>
|
||||
</Public>
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
import Public from '../../layouts/Public.astro';
|
||||
|
||||
interface ApiTestimonial {
|
||||
id: number;
|
||||
user_id: number;
|
||||
user_fullname: string;
|
||||
role: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
is_deleted: boolean;
|
||||
}
|
||||
|
||||
let testimonials: ApiTestimonial[] = [];
|
||||
try {
|
||||
const res = await fetch('https://api.imphnen.dev/v1/landing/cms/testimonials');
|
||||
const json = await res.json();
|
||||
testimonials = (json.data || []).filter((t: ApiTestimonial) => !t.is_deleted);
|
||||
} catch {
|
||||
testimonials = [];
|
||||
}
|
||||
|
||||
const AVATAR_COLORS = [
|
||||
'bg-primary-500 text-white',
|
||||
'bg-blue-500 text-white',
|
||||
'bg-green-500 text-white',
|
||||
'bg-purple-500 text-white',
|
||||
'bg-orange-500 text-white',
|
||||
'bg-pink-500 text-white',
|
||||
];
|
||||
|
||||
function getAvatarColor(index: number) {
|
||||
return AVATAR_COLORS[index % AVATAR_COLORS.length];
|
||||
}
|
||||
|
||||
function getInitial(name: string) {
|
||||
return name.charAt(0).toUpperCase();
|
||||
}
|
||||
---
|
||||
|
||||
<Public title="Testimonial - IMPHNEN">
|
||||
<div class="min-h-screen">
|
||||
<div class="py-16 px-4 text-center border-b border-border">
|
||||
<svg class="size-14 mx-auto my-4 text-foreground" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M0 2a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H4a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h.5a.5.5 0 0 1 0 1H4a2 2 0 0 1-2-2V8a3 3 0 0 1 3-3h.5V2H2zM9 2a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h.5a.5.5 0 0 1 0 1H13a2 2 0 0 1-2-2V8a3 3 0 0 1 3-3h.5V2H11z"/>
|
||||
</svg>
|
||||
<h1 class="text-4xl font-bold mb-4 text-foreground">Testimonial</h1>
|
||||
<p class="max-w-2xl mx-auto text-lg mb-8 text-balance text-muted-foreground">
|
||||
Menampilkan pengalaman dan cerita para member yang telah join ke dalam
|
||||
komunitas kami
|
||||
</p>
|
||||
<a
|
||||
href="/testimonials/submit"
|
||||
class="inline-flex items-center justify-center rounded-lg bg-primary text-primary-foreground px-6 py-3 text-sm font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Tulis Testimonialmu
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-8 md:gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 my-16 container">
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<div class="p-6 bg-white rounded-xl shadow-sm hover:shadow-md transition-shadow duration-300">
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class={`w-12 h-12 rounded-full flex items-center justify-center text-lg font-semibold ${getAvatarColor(index)}`}>
|
||||
{getInitial(testimonial.user_fullname)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900">
|
||||
{testimonial.user_fullname}
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600">{testimonial.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-gray-600 relative">
|
||||
<svg class="text-primary-500/30 w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 512 512"><path d="M464 256h-80v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8c-88.4 0-160 71.6-160 160v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48zm-288 0H96v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8C71.6 32 0 103.6 0 192v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"/></svg>
|
||||
<p class="text-sm/relaxed">{testimonial.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Public>
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
import Public from '../../layouts/Public.astro';
|
||||
---
|
||||
|
||||
<Public title="Submit Testimonial - IMPHNEN">
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<p class="text-muted-foreground">Coming soon.</p>
|
||||
</div>
|
||||
</Public>
|
||||
Reference in New Issue
Block a user