v2
This commit is contained in:
parent
ad5e9eae31
commit
8c8258f7b8
@ -0,0 +1,76 @@
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {import("sequelize").QueryInterface} queryInterface
|
||||
* @param {import("sequelize").Sequelize} Sequelize
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async up(queryInterface, Sequelize) {
|
||||
const createdAt = new Date();
|
||||
const updatedAt = new Date();
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO "rolesPermissionsPermissions" ("createdAt", "updatedAt", "roles_permissionsId", "permissionId")
|
||||
SELECT
|
||||
NOW(),
|
||||
NOW(),
|
||||
(SELECT id FROM "roles" WHERE name = 'Public'),
|
||||
(SELECT id FROM "permissions" WHERE name = 'READ_STARTUPS')
|
||||
WHERE EXISTS (SELECT id FROM "roles" WHERE name = 'Public')
|
||||
AND EXISTS (SELECT id FROM "permissions" WHERE name = 'READ_STARTUPS')
|
||||
ON CONFLICT DO NOTHING;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO "rolesPermissionsPermissions" ("createdAt", "updatedAt", "roles_permissionsId", "permissionId")
|
||||
SELECT
|
||||
NOW(),
|
||||
NOW(),
|
||||
(SELECT id FROM "roles" WHERE name = 'Public'),
|
||||
(SELECT id FROM "permissions" WHERE name = 'READ_STARTUP_CATEGORIES')
|
||||
WHERE EXISTS (SELECT id FROM "roles" WHERE name = 'Public')
|
||||
AND EXISTS (SELECT id FROM "permissions" WHERE name = 'READ_STARTUP_CATEGORIES')
|
||||
ON CONFLICT DO NOTHING;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO "rolesPermissionsPermissions" ("createdAt", "updatedAt", "roles_permissionsId", "permissionId")
|
||||
SELECT
|
||||
NOW(),
|
||||
NOW(),
|
||||
(SELECT id FROM "roles" WHERE name = 'Public'),
|
||||
(SELECT id FROM "permissions" WHERE name = 'READ_STARTUP_TAGS')
|
||||
WHERE EXISTS (SELECT id FROM "roles" WHERE name = 'Public')
|
||||
AND EXISTS (SELECT id FROM "permissions" WHERE name = 'READ_STARTUP_TAGS')
|
||||
ON CONFLICT DO NOTHING;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO "rolesPermissionsPermissions" ("createdAt", "updatedAt", "roles_permissionsId", "permissionId")
|
||||
SELECT
|
||||
NOW(),
|
||||
NOW(),
|
||||
(SELECT id FROM "roles" WHERE name = 'Public'),
|
||||
(SELECT id FROM "permissions" WHERE name = 'READ_STARTUP_TAG_ASSIGNMENTS')
|
||||
WHERE EXISTS (SELECT id FROM "roles" WHERE name = 'Public')
|
||||
AND EXISTS (SELECT id FROM "permissions" WHERE name = 'READ_STARTUP_TAG_ASSIGNMENTS')
|
||||
ON CONFLICT DO NOTHING;
|
||||
`);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import("sequelize").QueryInterface} queryInterface
|
||||
* @param {import("sequelize").Sequelize} Sequelize
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
DELETE FROM "rolesPermissionsPermissions"
|
||||
WHERE "roles_permissionsId" = (SELECT id FROM "roles" WHERE name = 'Public')
|
||||
AND "permissionId" IN (
|
||||
SELECT id FROM "permissions"
|
||||
WHERE name IN ('READ_STARTUPS', 'READ_STARTUP_CATEGORIES', 'READ_STARTUP_TAGS', 'READ_STARTUP_TAG_ASSIGNMENTS')
|
||||
);
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -117,9 +117,9 @@ app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoute
|
||||
|
||||
app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes);
|
||||
|
||||
app.use('/api/startup_categories', passport.authenticate('jwt', {session: false}), startup_categoriesRoutes);
|
||||
app.use('/api/startup_categories', startup_categoriesRoutes);
|
||||
|
||||
app.use('/api/startups', passport.authenticate('jwt', {session: false}), startupsRoutes);
|
||||
app.use('/api/startups', startupsRoutes);
|
||||
|
||||
app.use('/api/startup_tags', passport.authenticate('jwt', {session: false}), startup_tagsRoutes);
|
||||
|
||||
|
||||
8
frontend/next-i18next.config.mjs
Normal file
8
frontend/next-i18next.config.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'ru', 'fr', 'es', 'de'],
|
||||
},
|
||||
ns: ['common', 'translation'],
|
||||
defaultNS: 'common',
|
||||
};
|
||||
@ -2,12 +2,13 @@
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
|
||||
const output = process.env.NODE_ENV === 'production' ? 'export' : 'standalone';
|
||||
const nextConfig = {
|
||||
trailingSlash: true,
|
||||
const nextConfig = {
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'ru', 'fr', 'es', 'de'],
|
||||
},
|
||||
trailingSlash: true,
|
||||
distDir: 'build',
|
||||
output,
|
||||
basePath: "",
|
||||
devIndicators: {
|
||||
position: 'bottom-left',
|
||||
},
|
||||
@ -26,7 +27,15 @@ trailingSlash: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/startup/:id',
|
||||
destination: '/web_pages/startup-details/?id=:id',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
export default nextConfig
|
||||
|
||||
1
frontend/public/locales/en/translation.json
Normal file
1
frontend/public/locales/en/translation.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
1
frontend/public/locales/ru/common.json
Normal file
1
frontend/public/locales/ru/common.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
1
frontend/public/locales/ru/translation.json
Normal file
1
frontend/public/locales/ru/translation.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
72
frontend/src/components/StartupSummaryTile.tsx
Normal file
72
frontend/src/components/StartupSummaryTile.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React from 'react'
|
||||
import { mdiRocketLaunch, mdiMapMarker, mdiTagOutline, mdiChartLine, mdiAccountGroupOutline } from '@mdi/js'
|
||||
import BaseIcon from './BaseIcon'
|
||||
|
||||
interface Startup {
|
||||
id: string
|
||||
name: string
|
||||
tagline: string
|
||||
industry: string
|
||||
location: string
|
||||
stage: string
|
||||
business_model: string
|
||||
description?: string
|
||||
logo?: { url: string }[]
|
||||
}
|
||||
|
||||
const StartupSummaryTile = ({ startup }: { startup: Startup }) => {
|
||||
const logoUrl = startup.logo && startup.logo[0] ? startup.logo[0].url : null
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:shadow-blue-100 transition-all duration-300">
|
||||
<div className="flex items-center mb-6">
|
||||
{logoUrl ? (
|
||||
<img src={logoUrl} alt={startup.name} className="w-16 h-16 rounded-2xl object-cover border border-slate-100 mr-4" />
|
||||
) : (
|
||||
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600 border border-blue-100 mr-4">
|
||||
<BaseIcon path={mdiRocketLaunch} size={32} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="text-xl font-black text-slate-900 leading-tight">{startup.name}</h3>
|
||||
<p className="text-sm font-medium text-slate-400 mt-1 uppercase tracking-wider">{startup.industry}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-slate-600 mb-6 font-medium line-clamp-2 italic leading-snug">
|
||||
"{startup.tagline}"
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center text-slate-500 font-medium">
|
||||
<div className="w-8 h-8 bg-slate-50 rounded-lg flex items-center justify-center mr-3 text-slate-400">
|
||||
<BaseIcon path={mdiMapMarker} size={18} />
|
||||
</div>
|
||||
<span className="text-sm">{startup.location || 'Remote'}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-slate-500 font-medium">
|
||||
<div className="w-8 h-8 bg-slate-50 rounded-lg flex items-center justify-center mr-3 text-slate-400">
|
||||
<BaseIcon path={mdiTagOutline} size={18} />
|
||||
</div>
|
||||
<span className="text-sm capitalize">{startup.stage} Stage</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-slate-500 font-medium">
|
||||
<div className="w-8 h-8 bg-slate-50 rounded-lg flex items-center justify-center mr-3 text-slate-400">
|
||||
<BaseIcon path={mdiChartLine} size={18} />
|
||||
</div>
|
||||
<span className="text-sm uppercase">{startup.business_model} Model</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-slate-50">
|
||||
<button className="w-full bg-slate-900 hover:bg-slate-800 text-white py-3 rounded-2xl font-bold transition shadow-lg shadow-slate-200">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StartupSummaryTile
|
||||
@ -1,166 +1,86 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import CardBox from '../components/CardBox';
|
||||
import SectionFullScreen from '../components/SectionFullScreen';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import BaseDivider from '../components/BaseDivider';
|
||||
import BaseButtons from '../components/BaseButtons';
|
||||
import { getPageTitle } from '../config';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
||||
import {
|
||||
mdiRocketLaunch,
|
||||
mdiBriefcaseAccountOutline,
|
||||
mdiArrowRight
|
||||
} from '@mdi/js';
|
||||
import BaseIcon from '../components/BaseIcon';
|
||||
|
||||
export default function LandingPage() {
|
||||
return (
|
||||
<div className="bg-slate-50 min-h-screen font-sans text-slate-900">
|
||||
<Head>
|
||||
<title>{getPageTitle('Welcome')}</title>
|
||||
<meta name="description" content="Startup & Investor Marketplace" />
|
||||
</Head>
|
||||
|
||||
export default function Starter() {
|
||||
const [illustrationImage, setIllustrationImage] = useState({
|
||||
src: undefined,
|
||||
photographer: undefined,
|
||||
photographer_url: undefined,
|
||||
})
|
||||
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
||||
const [contentType, setContentType] = useState('video');
|
||||
const [contentPosition, setContentPosition] = useState('left');
|
||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||
{/* Hero Section */}
|
||||
<section className="pt-20 pb-32">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="bg-blue-600 p-3 rounded-2xl shadow-lg shadow-blue-200">
|
||||
<BaseIcon path={mdiRocketLaunch} className="text-white" size={40} />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-5xl md:text-6xl font-black tracking-tight mb-6 text-slate-900">
|
||||
The Future of <span className="text-blue-600">Investment</span>
|
||||
</h1>
|
||||
<p className="mt-4 max-w-2xl text-xl text-slate-500 mx-auto mb-16">
|
||||
Connecting visionary startups with strategic investors through AI-driven insights and a seamless marketplace.
|
||||
</p>
|
||||
|
||||
const title = 'Startup Profiles Marketplace'
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||
{/* Startup Path */}
|
||||
<Link href="/startup/submit-info" className="group relative bg-white p-10 rounded-3xl shadow-sm border border-slate-200 hover:border-blue-500 hover:shadow-xl hover:shadow-blue-100 transition-all duration-300 text-left">
|
||||
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mb-6 text-blue-600 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
||||
<BaseIcon path={mdiRocketLaunch} size={32} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-slate-900 mb-3 flex items-center">
|
||||
I am a Startup
|
||||
<BaseIcon path={mdiArrowRight} className="ml-2 opacity-0 group-hover:opacity-100 transform translate-x-[-10px] group-hover:translate-x-0 transition-all" size={24} />
|
||||
</h3>
|
||||
<p className="text-slate-500 text-lg leading-relaxed">
|
||||
Showcase your startup, upload key documents, and get AI-powered insights to attract the right investors.
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
// Fetch Pexels image/video
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const image = await getPexelsImage();
|
||||
const video = await getPexelsVideo();
|
||||
setIllustrationImage(image);
|
||||
setIllustrationVideo(video);
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
{/* Investor Path */}
|
||||
<Link href="/investor" className="group relative bg-white p-10 rounded-3xl shadow-sm border border-slate-200 hover:border-emerald-500 hover:shadow-xl hover:shadow-emerald-100 transition-all duration-300 text-left">
|
||||
<div className="w-16 h-16 bg-emerald-50 rounded-2xl flex items-center justify-center mb-6 text-emerald-600 group-hover:bg-emerald-600 group-hover:text-white transition-colors">
|
||||
<BaseIcon path={mdiBriefcaseAccountOutline} size={32} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-slate-900 mb-3 flex items-center">
|
||||
I am an Investor
|
||||
<BaseIcon path={mdiArrowRight} className="ml-2 opacity-0 group-hover:opacity-100 transform translate-x-[-10px] group-hover:translate-x-0 transition-all" size={24} />
|
||||
</h3>
|
||||
<p className="text-slate-500 text-lg leading-relaxed">
|
||||
Discover curated investment opportunities tailored to your risk profile and ethical preferences.
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
const imageBlock = (image) => (
|
||||
<div
|
||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
||||
style={{
|
||||
backgroundImage: `${
|
||||
image
|
||||
? `url(${image?.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
>
|
||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={image?.photographer_url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Photo by {image?.photographer} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
{/* Footer */}
|
||||
<footer className="py-12 border-t border-slate-200 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="flex items-center mb-4 md:mb-0">
|
||||
<span className="font-bold text-xl text-slate-900 tracking-tight">StartupMarketplace</span>
|
||||
</div>
|
||||
<div className="text-slate-400 text-sm">
|
||||
© {new Date().getFullYear()} StartupMarketplace. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
||||
const videoBlock = (video) => {
|
||||
if (video?.video_files?.length > 0) {
|
||||
return (
|
||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
||||
<video
|
||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
>
|
||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={video?.user?.url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Video by {video.user.name} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
contentPosition === 'background'
|
||||
? {
|
||||
backgroundImage: `${
|
||||
illustrationImage
|
||||
? `url(${illustrationImage.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<Head>
|
||||
<title>{getPageTitle('Starter Page')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<div
|
||||
className={`flex ${
|
||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||
} min-h-screen w-full`}
|
||||
>
|
||||
{contentType === 'image' && contentPosition !== 'background'
|
||||
? imageBlock(illustrationImage)
|
||||
: null}
|
||||
{contentType === 'video' && contentPosition !== 'background'
|
||||
? videoBlock(illustrationVideo)
|
||||
: null}
|
||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||
<CardBoxComponentTitle title="Welcome to your Startup Profiles Marketplace app!"/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
||||
<p className='text-center text-gray-500'>For guides and documentation please check
|
||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||
</div>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
href='/login'
|
||||
label='Login'
|
||||
color='info'
|
||||
className='w-full'
|
||||
/>
|
||||
|
||||
</BaseButtons>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Starter.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
LandingPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
|
||||
|
||||
75
frontend/src/pages/investor/index.tsx
Normal file
75
frontend/src/pages/investor/index.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import LayoutGuest from '../../layouts/Guest';
|
||||
import { getPageTitle } from '../../config';
|
||||
import {
|
||||
mdiBriefcaseAccountOutline,
|
||||
mdiChartPie,
|
||||
mdiTableLarge,
|
||||
mdiArrowRight
|
||||
} from '@mdi/js';
|
||||
import BaseIcon from '../../components/BaseIcon';
|
||||
|
||||
export default function InvestorChoicePage() {
|
||||
return (
|
||||
<div className="bg-slate-50 min-h-screen font-sans text-slate-900">
|
||||
<Head>
|
||||
<title>{getPageTitle('Investor Strategy')}</title>
|
||||
<meta name="description" content="Choose your investment strategy" />
|
||||
</Head>
|
||||
|
||||
{/* Strategy Choice Section */}
|
||||
<section className="pt-20 pb-32">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="bg-emerald-600 p-3 rounded-2xl shadow-lg shadow-emerald-200">
|
||||
<BaseIcon path={mdiBriefcaseAccountOutline} className="text-white" size={40} />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-5xl md:text-6xl font-black tracking-tight mb-6 text-slate-900">
|
||||
Choose Your <span className="text-emerald-600">Strategy</span>
|
||||
</h1>
|
||||
<p className="mt-4 max-w-2xl text-xl text-slate-500 mx-auto mb-16">
|
||||
Whether you want AI-curated opportunities or prefer to explore the marketplace, we've got you covered.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||
{/* Tailored Flow */}
|
||||
<Link href="/investor/tailored" className="group relative bg-white p-10 rounded-3xl shadow-sm border border-slate-200 hover:border-emerald-500 hover:shadow-xl hover:shadow-emerald-100 transition-all duration-300 text-left">
|
||||
<div className="w-16 h-16 bg-emerald-50 rounded-2xl flex items-center justify-center mb-6 text-emerald-600 group-hover:bg-emerald-600 group-hover:text-white transition-colors">
|
||||
<BaseIcon path={mdiChartPie} size={32} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-slate-900 mb-3 flex items-center">
|
||||
Tailored Strategy
|
||||
<BaseIcon path={mdiArrowRight} className="ml-2 opacity-0 group-hover:opacity-100 transform translate-x-[-10px] group-hover:translate-x-0 transition-all" size={24} />
|
||||
</h3>
|
||||
<p className="text-slate-500 text-lg leading-relaxed">
|
||||
Answer a few simple questions about your risk profile and preferences to receive a personalized portfolio visualization.
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
{/* Manual Flow */}
|
||||
<Link href="/investor/marketplace" className="group relative bg-white p-10 rounded-3xl shadow-sm border border-slate-200 hover:border-blue-500 hover:shadow-xl hover:shadow-blue-100 transition-all duration-300 text-left">
|
||||
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mb-6 text-blue-600 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
||||
<BaseIcon path={mdiTableLarge} size={32} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-slate-900 mb-3 flex items-center">
|
||||
Manual Exploration
|
||||
<BaseIcon path={mdiArrowRight} className="ml-2 opacity-0 group-hover:opacity-100 transform translate-x-[-10px] group-hover:translate-x-0 transition-all" size={24} />
|
||||
</h3>
|
||||
<p className="text-slate-500 text-lg leading-relaxed">
|
||||
Browse our full marketplace of startups, apply advanced filters, and search for specific opportunities.
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
InvestorChoicePage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
161
frontend/src/pages/investor/marketplace.tsx
Normal file
161
frontend/src/pages/investor/marketplace.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Head from 'next/head'
|
||||
import LayoutGuest from '../../layouts/Guest'
|
||||
import SectionMain from '../../components/SectionMain'
|
||||
import { getPageTitle } from '../../config'
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
|
||||
import { fetch as fetchStartups } from '../../stores/startups/startupsSlice'
|
||||
import StartupSummaryTile from '../../components/StartupSummaryTile'
|
||||
import { mdiMagnify, mdiFilterVariant, mdiClose } from '@mdi/js'
|
||||
import BaseIcon from '../../components/BaseIcon'
|
||||
import { Formik, Form, Field } from 'formik'
|
||||
|
||||
export default function MarketplacePage() {
|
||||
const dispatch = useAppDispatch()
|
||||
const { startups, loading } = useAppSelector((state) => state.startups)
|
||||
const [search, setSearch] = useState('')
|
||||
const [filters, setFilters] = useState({
|
||||
industry: '',
|
||||
stage: '',
|
||||
business_model: '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const queryParts = []
|
||||
if (search) queryParts.push(`name_contains=${search}`)
|
||||
if (filters.industry) queryParts.push(`industry_contains=${filters.industry}`)
|
||||
if (filters.stage) queryParts.push(`stage=${filters.stage}`)
|
||||
if (filters.business_model) queryParts.push(`business_model=${filters.business_model}`)
|
||||
|
||||
const query = queryParts.length > 0 ? `?${queryParts.join('&')}` : ''
|
||||
dispatch(fetchStartups({ query }))
|
||||
}, [search, filters, dispatch])
|
||||
|
||||
return (
|
||||
<div className="bg-slate-50 min-h-screen font-sans text-slate-900">
|
||||
<Head>
|
||||
<title>{getPageTitle('Startup Marketplace')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionMain>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between mb-12 space-y-4 md:space-y-0">
|
||||
<div>
|
||||
<h1 className="text-4xl font-black text-slate-900">Startup Marketplace</h1>
|
||||
<p className="text-slate-500 text-lg mt-2">Discover the next big opportunity.</p>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-md w-full">
|
||||
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||
<BaseIcon path={mdiMagnify} className="text-slate-400" size={24} />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className="block w-full pl-12 pr-4 py-4 border-none rounded-2xl bg-white shadow-xl shadow-blue-50 focus:ring-2 focus:ring-blue-500 transition-all text-lg font-medium placeholder-slate-400"
|
||||
placeholder="Search startups..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-8">
|
||||
{/* Sidebar Filters */}
|
||||
<aside className="w-full md:w-64 flex-shrink-0">
|
||||
<div className="bg-white p-6 rounded-3xl shadow-sm border border-slate-200 sticky top-24">
|
||||
<div className="flex items-center mb-6 text-slate-900">
|
||||
<BaseIcon path={mdiFilterVariant} className="mr-2" size={24} />
|
||||
<h2 className="font-bold text-xl uppercase tracking-wider">Filters</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-slate-500 uppercase tracking-widest mb-3">Industry</label>
|
||||
<select
|
||||
className="w-full bg-slate-50 border-none rounded-xl py-3 px-4 font-medium focus:ring-2 focus:ring-blue-500 transition-all"
|
||||
value={filters.industry}
|
||||
onChange={(e) => setFilters({ ...filters, industry: e.target.value })}
|
||||
>
|
||||
<option value="">All Industries</option>
|
||||
<option value="fintech">Fintech</option>
|
||||
<option value="healthtech">Healthtech</option>
|
||||
<option value="saas">SaaS</option>
|
||||
<option value="edtech">Edtech</option>
|
||||
<option value="ecommerce">E-commerce</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-slate-500 uppercase tracking-widest mb-3">Stage</label>
|
||||
<select
|
||||
className="w-full bg-slate-50 border-none rounded-xl py-3 px-4 font-medium focus:ring-2 focus:ring-blue-500 transition-all"
|
||||
value={filters.stage}
|
||||
onChange={(e) => setFilters({ ...filters, stage: e.target.value })}
|
||||
>
|
||||
<option value="">All Stages</option>
|
||||
<option value="idea">Idea</option>
|
||||
<option value="mvp">MVP</option>
|
||||
<option value="beta">Beta</option>
|
||||
<option value="launched">Launched</option>
|
||||
<option value="growth">Growth</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-slate-500 uppercase tracking-widest mb-3">Business Model</label>
|
||||
<select
|
||||
className="w-full bg-slate-50 border-none rounded-xl py-3 px-4 font-medium focus:ring-2 focus:ring-blue-500 transition-all"
|
||||
value={filters.business_model}
|
||||
onChange={(e) => setFilters({ ...filters, business_model: e.target.value })}
|
||||
>
|
||||
<option value="">All Models</option>
|
||||
<option value="saas">SaaS</option>
|
||||
<option value="marketplace">Marketplace</option>
|
||||
<option value="b2b">B2B</option>
|
||||
<option value="b2c">B2C</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setFilters({ industry: '', stage: '', business_model: '' })}
|
||||
className="w-full flex items-center justify-center py-3 text-slate-400 hover:text-red-500 font-bold transition-colors"
|
||||
>
|
||||
<BaseIcon path={mdiClose} className="mr-1" size={18} />
|
||||
Clear Filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
) : startups && startups.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{startups.map((startup) => (
|
||||
<StartupSummaryTile key={startup.id} startup={startup} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white p-20 rounded-3xl shadow-sm border border-slate-100 text-center">
|
||||
<div className="bg-slate-50 w-24 h-24 rounded-full flex items-center justify-center mx-auto mb-6 text-slate-300">
|
||||
<BaseIcon path={mdiMagnify} size={48} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-slate-900 mb-2">No startups found</h3>
|
||||
<p className="text-slate-500 max-w-xs mx-auto">Try adjusting your filters or search terms to find what you're looking for.</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
MarketplacePage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>
|
||||
}
|
||||
209
frontend/src/pages/investor/tailored.tsx
Normal file
209
frontend/src/pages/investor/tailored.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import Head from 'next/head'
|
||||
import LayoutGuest from '../../layouts/Guest'
|
||||
import SectionMain from '../../components/SectionMain'
|
||||
import { getPageTitle } from '../../config'
|
||||
import CardBox from '../../components/CardBox'
|
||||
import BaseButton from '../../components/BaseButton'
|
||||
import { mdiChartPie, mdiArrowRight, mdiChevronLeft, mdiHelpCircleOutline } from '@mdi/js'
|
||||
import BaseIcon from '../../components/BaseIcon'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
|
||||
import { fetch as fetchStartups } from '../../stores/startups/startupsSlice'
|
||||
import StartupSummaryTile from '../../components/StartupSummaryTile'
|
||||
|
||||
// ApexCharts needs to be dynamic for Next.js SSR
|
||||
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false })
|
||||
|
||||
const questions = [
|
||||
{
|
||||
id: 'risk',
|
||||
question: 'What is your risk tolerance?',
|
||||
options: [
|
||||
{ value: 'low', label: 'Conservative', description: 'Focus on stable, late-stage startups.' },
|
||||
{ value: 'medium', label: 'Balanced', description: 'Mix of growth and early-stage companies.' },
|
||||
{ value: 'high', label: 'Aggressive', description: 'High-reward early-stage opportunities.' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sector',
|
||||
question: 'Which sectors are you most interested in?',
|
||||
options: [
|
||||
{ value: 'fintech', label: 'Fintech', description: 'Financial technology and services.' },
|
||||
{ value: 'healthtech', label: 'Healthtech', description: 'Healthcare and biotech innovations.' },
|
||||
{ value: 'saas', label: 'SaaS', description: 'Software as a Service solutions.' },
|
||||
{ value: 'sustainability', label: 'Sustainability', description: 'Green energy and eco-friendly tech.' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'ethics',
|
||||
question: 'How important are ethical/ESG factors?',
|
||||
options: [
|
||||
{ value: 'essential', label: 'Essential', description: 'Only show companies with high ESG scores.' },
|
||||
{ value: 'important', label: 'Important', description: 'Prefer companies with ethical practices.' },
|
||||
{ value: 'neutral', label: 'Neutral', description: 'Ethics are not the primary driver.' },
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export default function TailoredStrategyPage() {
|
||||
const [step, setStep] = useState(0)
|
||||
const [answers, setAnswers] = useState<Record<string, string>>({})
|
||||
const [showResult, setShowResult] = useState(false)
|
||||
const dispatch = useAppDispatch()
|
||||
const { startups } = useAppSelector((state) => state.startups)
|
||||
const [hoveredStartup, setHoveredStartup] = useState<any>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (showResult) {
|
||||
dispatch(fetchStartups({ query: '' }))
|
||||
}
|
||||
}, [showResult, dispatch])
|
||||
|
||||
const handleAnswer = (questionId: string, value: string) => {
|
||||
setAnswers({ ...answers, [questionId]: value })
|
||||
if (step < questions.length - 1) {
|
||||
setStep(step + 1)
|
||||
} else {
|
||||
setShowResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data for the pie chart based on startups
|
||||
const chartOptions: any = {
|
||||
chart: {
|
||||
type: 'pie',
|
||||
events: {
|
||||
dataPointMouseEnter: (event, chartContext, config) => {
|
||||
const index = config.dataPointIndex
|
||||
if (startups && startups[index]) {
|
||||
setHoveredStartup(startups[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labels: startups?.slice(0, 5).map(s => s.name) || [],
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
},
|
||||
colors: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'],
|
||||
dataLabels: {
|
||||
enabled: true
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
y: {
|
||||
formatter: (val) => `${val}% Allocation`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const chartSeries = [30, 25, 20, 15, 10]
|
||||
|
||||
return (
|
||||
<div className="bg-slate-50 min-h-screen font-sans text-slate-900">
|
||||
<Head>
|
||||
<title>{getPageTitle('Tailored Strategy')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionMain>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{!showResult ? (
|
||||
<div className="space-y-12">
|
||||
<div className="text-center">
|
||||
<div className="inline-flex items-center justify-center p-3 bg-emerald-100 text-emerald-600 rounded-2xl mb-6">
|
||||
<BaseIcon path={mdiHelpCircleOutline} size={32} />
|
||||
</div>
|
||||
<h1 className="text-4xl font-black text-slate-900 mb-4">Personalize Your Portfolio</h1>
|
||||
<p className="text-slate-500 text-lg">Question {step + 1} of {questions.length}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
<h2 className="text-2xl font-bold text-center text-slate-800 mb-4">
|
||||
{questions[step].question}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{questions[step].options.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => handleAnswer(questions[step].id, option.value)}
|
||||
className="bg-white p-8 rounded-3xl border-2 border-transparent hover:border-emerald-500 hover:shadow-xl transition-all duration-300 text-left group"
|
||||
>
|
||||
<div className="w-12 h-12 bg-emerald-50 rounded-xl flex items-center justify-center mb-6 text-emerald-600 group-hover:bg-emerald-600 group-hover:text-white transition-colors">
|
||||
<BaseIcon path={mdiArrowRight} size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-2">{option.label}</h3>
|
||||
<p className="text-slate-500">{option.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{step > 0 && (
|
||||
<div className="flex justify-center">
|
||||
<BaseButton
|
||||
label="Back"
|
||||
icon={mdiChevronLeft}
|
||||
onClick={() => setStep(step - 1)}
|
||||
className="text-slate-400 hover:text-slate-600 font-bold"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-12">
|
||||
<div className="text-center">
|
||||
<div className="inline-flex items-center justify-center p-3 bg-blue-100 text-blue-600 rounded-2xl mb-6">
|
||||
<BaseIcon path={mdiChartPie} size={32} />
|
||||
</div>
|
||||
<h1 className="text-4xl font-black text-slate-900 mb-4">Your Tailored Portfolio</h1>
|
||||
<p className="text-slate-500 text-lg">Based on your preferences, we recommend the following allocation.</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div className="bg-white p-8 rounded-3xl shadow-xl border border-slate-100">
|
||||
<Chart
|
||||
options={chartOptions}
|
||||
series={chartSeries}
|
||||
type="pie"
|
||||
width="100%"
|
||||
/>
|
||||
<p className="text-center text-sm text-slate-400 mt-6 italic">
|
||||
Hover over segments to see individual startup details
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-2xl font-bold text-slate-900">Featured Opportunities</h3>
|
||||
{hoveredStartup ? (
|
||||
<div className="transform scale-105 transition-all duration-500">
|
||||
<StartupSummaryTile startup={hoveredStartup} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-slate-100 border-2 border-dashed border-slate-200 rounded-3xl p-12 text-center text-slate-400">
|
||||
<BaseIcon path={mdiArrowRight} size={48} className="mx-auto mb-4 opacity-20" />
|
||||
<p className="font-medium text-lg">Hover over the chart to explore startups</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center pt-8">
|
||||
<BaseButton
|
||||
label="View Full Marketplace"
|
||||
color="info"
|
||||
className="px-12 py-4 rounded-2xl font-bold text-xl shadow-xl hover:shadow-blue-200 transition-all"
|
||||
onClick={() => router.push('/investor/marketplace')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SectionMain>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TailoredStrategyPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>
|
||||
}
|
||||
156
frontend/src/pages/startup/ai-insights.tsx
Normal file
156
frontend/src/pages/startup/ai-insights.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import { mdiRocketLaunch, mdiCheckDecagram, mdiCreation } from '@mdi/js'
|
||||
import Head from 'next/head'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import CardBox from '../../components/CardBox'
|
||||
import LayoutGuest from '../../layouts/Guest'
|
||||
import SectionMain from '../../components/SectionMain'
|
||||
import { getPageTitle } from '../../config'
|
||||
import BaseButton from '../../components/BaseButton'
|
||||
import { fetch } from '../../stores/startups/startupsSlice'
|
||||
import { aiResponse } from '../../stores/openAiSlice'
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
|
||||
import { useRouter } from 'next/router'
|
||||
import BaseIcon from '../../components/BaseIcon'
|
||||
import StartupSummaryTile from '../../components/StartupSummaryTile'
|
||||
|
||||
const AIInsightsPage = () => {
|
||||
const router = useRouter()
|
||||
const { id } = router.query
|
||||
const dispatch = useAppDispatch()
|
||||
const [startup, setStartup] = useState<any>(null)
|
||||
const { isAskingResponse, aiResponse: rawAiResponse } = useAppSelector((state) => state.openAi)
|
||||
const [insights, setInsights] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
dispatch(fetch({ id })).then((action) => {
|
||||
if (fetch.fulfilled.match(action)) {
|
||||
setStartup(action.payload)
|
||||
|
||||
// Trigger AI Insights
|
||||
const prompt = `Analyze this startup and provide 3 key insights for investors,
|
||||
focusing on market potential, competitive advantage, and growth strategy.
|
||||
Startup Name: ${action.payload.name}
|
||||
Tagline: ${action.payload.tagline}
|
||||
Industry: ${action.payload.industry}
|
||||
Description: ${action.payload.description}
|
||||
Stage: ${action.payload.stage}
|
||||
Business Model: ${action.payload.business_model}
|
||||
`
|
||||
|
||||
dispatch(aiResponse({
|
||||
input: [
|
||||
{ role: 'system', content: 'You are an expert venture capital analyst.' },
|
||||
{ role: 'user', content: prompt }
|
||||
]
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id, dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
if (rawAiResponse && rawAiResponse.success) {
|
||||
// Extract text from Responses API format
|
||||
const output = rawAiResponse.data?.output;
|
||||
if (Array.isArray(output)) {
|
||||
const message = output.find(o => o.type === 'message');
|
||||
if (message && Array.isArray(message.content)) {
|
||||
const textObj = message.content.find(c => c.type === 'output_text');
|
||||
if (textObj) {
|
||||
setInsights(textObj.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [rawAiResponse])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Step 3: AI Insights')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Progress Bar */}
|
||||
<div className="flex items-center justify-between mb-12">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-green-500 text-white rounded-full flex items-center justify-center font-bold mb-2 shadow-lg shadow-green-100">1</div>
|
||||
<span className="text-sm font-semibold text-green-500">Info</span>
|
||||
</div>
|
||||
<div className="flex-1 h-1 bg-green-500 mx-4 -mt-6"></div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-green-500 text-white rounded-full flex items-center justify-center font-bold mb-2 shadow-lg shadow-green-100">2</div>
|
||||
<span className="text-sm font-semibold text-green-500">Documents</span>
|
||||
</div>
|
||||
<div className="flex-1 h-1 bg-blue-600 mx-4 -mt-6"></div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center font-bold mb-2 shadow-lg shadow-blue-200">3</div>
|
||||
<span className="text-sm font-semibold text-blue-600">Insights</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="text-3xl font-black text-slate-900 mb-3 flex items-center justify-center">
|
||||
<BaseIcon path={mdiCreation} className="text-blue-600 mr-2" size={32} />
|
||||
AI-Powered Insights
|
||||
</h1>
|
||||
<p className="text-slate-500 text-lg">Our AI has analyzed your startup profile. Here's what we found.</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
|
||||
<div className="md:col-span-1">
|
||||
<h2 className="text-xl font-bold text-slate-900 mb-4">Startup Summary</h2>
|
||||
{startup && <StartupSummaryTile startup={startup} />}
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<CardBox className="h-full shadow-2xl border-none bg-blue-600 text-white">
|
||||
<div className="mb-6 flex items-center">
|
||||
<BaseIcon path={mdiCheckDecagram} size={24} className="mr-2" />
|
||||
<h2 className="text-xl font-bold">Investment Analysis</h2>
|
||||
</div>
|
||||
|
||||
{isAskingResponse ? (
|
||||
<div className="flex flex-col items-center justify-center h-64 space-y-4">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
|
||||
<p className="text-blue-100 font-medium">Analyzing market data and profile...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="prose prose-invert max-w-none">
|
||||
{insights ? (
|
||||
<div className="whitespace-pre-line text-blue-50 leading-relaxed text-lg">
|
||||
{insights}
|
||||
</div>
|
||||
) : (
|
||||
<p>AI Insights will appear here shortly.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center space-x-6">
|
||||
<BaseButton
|
||||
label="Finish & Go to Marketplace"
|
||||
color="info"
|
||||
className="px-12 py-4 rounded-2xl font-bold text-xl shadow-xl hover:shadow-blue-200 transition-all"
|
||||
onClick={() => router.push('/investor/marketplace')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
AIInsightsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutGuest>
|
||||
{page}
|
||||
</LayoutGuest>
|
||||
)
|
||||
}
|
||||
|
||||
export default AIInsightsPage
|
||||
153
frontend/src/pages/startup/submit-documents.tsx
Normal file
153
frontend/src/pages/startup/submit-documents.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import { mdiRocketLaunch, mdiArrowRight, mdiUpload } from '@mdi/js'
|
||||
import Head from 'next/head'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import CardBox from '../../components/CardBox'
|
||||
import LayoutGuest from '../../layouts/Guest'
|
||||
import SectionMain from '../../components/SectionMain'
|
||||
import { getPageTitle } from '../../config'
|
||||
import { Field, Form, Formik } from 'formik'
|
||||
import FormField from '../../components/FormField'
|
||||
import BaseButton from '../../components/BaseButton'
|
||||
import { update, fetch } from '../../stores/startups/startupsSlice'
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
|
||||
import { useRouter } from 'next/router'
|
||||
import FormFilePicker from '../../components/FormFilePicker'
|
||||
import FormImagePicker from '../../components/FormImagePicker'
|
||||
|
||||
const SubmitDocumentsPage = () => {
|
||||
const router = useRouter()
|
||||
const { id } = router.query
|
||||
const dispatch = useAppDispatch()
|
||||
const [initialValues, setInitialValues] = useState({
|
||||
logo: [],
|
||||
gallery_images: [],
|
||||
attachments: [],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
dispatch(fetch({ id })).then((action) => {
|
||||
if (fetch.fulfilled.match(action)) {
|
||||
setInitialValues({
|
||||
logo: action.payload.logo || [],
|
||||
gallery_images: action.payload.gallery_images || [],
|
||||
attachments: action.payload.attachments || [],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id, dispatch])
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
const resultAction = await dispatch(update({ id, data }))
|
||||
if (update.fulfilled.match(resultAction)) {
|
||||
await router.push(`/startup/ai-insights?id=${id}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Step 2: Submit Documents')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{/* Progress Bar */}
|
||||
<div className="flex items-center justify-between mb-12">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-green-500 text-white rounded-full flex items-center justify-center font-bold mb-2 shadow-lg shadow-green-100">1</div>
|
||||
<span className="text-sm font-semibold text-green-500">Info</span>
|
||||
</div>
|
||||
<div className="flex-1 h-1 bg-blue-600 mx-4 -mt-6"></div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center font-bold mb-2 shadow-lg shadow-blue-200">2</div>
|
||||
<span className="text-sm font-semibold text-blue-600">Documents</span>
|
||||
</div>
|
||||
<div className="flex-1 h-1 bg-gray-200 mx-4 -mt-6"></div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-gray-200 text-gray-500 rounded-full flex items-center justify-center font-bold mb-2">3</div>
|
||||
<span className="text-sm font-medium text-gray-400">Insights</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="text-3xl font-black text-slate-900 mb-3">Upload your documents</h1>
|
||||
<p className="text-slate-500 text-lg">Bring your vision to life with visuals and deck.</p>
|
||||
</div>
|
||||
|
||||
<CardBox className="shadow-2xl border-none">
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
enableReinitialize={true}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form className="space-y-8">
|
||||
<FormField label="Company Logo">
|
||||
<Field
|
||||
label="Logo"
|
||||
color="info"
|
||||
icon={mdiUpload}
|
||||
path={'startups/logo'}
|
||||
name="logo"
|
||||
id="logo"
|
||||
component={FormImagePicker}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Pitch Deck and Other Documents">
|
||||
<Field
|
||||
label="Attachments"
|
||||
color="info"
|
||||
icon={mdiUpload}
|
||||
path={'startups/attachments'}
|
||||
name="attachments"
|
||||
id="attachments"
|
||||
component={FormFilePicker}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Gallery Images">
|
||||
<Field
|
||||
label="Gallery"
|
||||
color="info"
|
||||
icon={mdiUpload}
|
||||
path={'startups/gallery_images'}
|
||||
name="gallery_images"
|
||||
id="gallery_images"
|
||||
component={FormImagePicker}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<div className="flex justify-between pt-4">
|
||||
<BaseButton
|
||||
label="Back"
|
||||
type="button"
|
||||
onClick={() => router.push(`/startup/submit-info?id=${id}`)}
|
||||
className="px-8 py-3 rounded-xl font-bold text-lg"
|
||||
/>
|
||||
<BaseButton
|
||||
type="submit"
|
||||
color="info"
|
||||
label="Get AI Insights"
|
||||
icon={mdiArrowRight}
|
||||
className="px-8 py-3 rounded-xl font-bold text-lg"
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
SubmitDocumentsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutGuest>
|
||||
{page}
|
||||
</LayoutGuest>
|
||||
)
|
||||
}
|
||||
|
||||
export default SubmitDocumentsPage
|
||||
139
frontend/src/pages/startup/submit-info.tsx
Normal file
139
frontend/src/pages/startup/submit-info.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import { mdiRocketLaunch, mdiArrowRight } from '@mdi/js'
|
||||
import Head from 'next/head'
|
||||
import React, { ReactElement } from 'react'
|
||||
import CardBox from '../../components/CardBox'
|
||||
import LayoutGuest from '../../layouts/Guest'
|
||||
import SectionMain from '../../components/SectionMain'
|
||||
import { getPageTitle } from '../../config'
|
||||
import { Field, Form, Formik } from 'formik'
|
||||
import FormField from '../../components/FormField'
|
||||
import BaseButton from '../../components/BaseButton'
|
||||
import { create } from '../../stores/startups/startupsSlice'
|
||||
import { useAppDispatch } from '../../stores/hooks'
|
||||
import { useRouter } from 'next/router'
|
||||
import BaseIcon from '../../components/BaseIcon'
|
||||
|
||||
const initialValues = {
|
||||
name: '',
|
||||
tagline: '',
|
||||
description: '',
|
||||
industry: '',
|
||||
location: '',
|
||||
stage: 'idea',
|
||||
business_model: 'saas',
|
||||
}
|
||||
|
||||
const SubmitInfoPage = () => {
|
||||
const router = useRouter()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
const resultAction = await dispatch(create(data))
|
||||
if (create.fulfilled.match(resultAction)) {
|
||||
const createdId = resultAction.payload.id
|
||||
await router.push(`/startup/submit-documents?id=${createdId}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Step 1: Submit Info')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{/* Progress Bar */}
|
||||
<div className="flex items-center justify-between mb-12">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center font-bold mb-2 shadow-lg shadow-blue-200">1</div>
|
||||
<span className="text-sm font-semibold text-blue-600">Info</span>
|
||||
</div>
|
||||
<div className="flex-1 h-1 bg-gray-200 mx-4 -mt-6"></div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-gray-200 text-gray-500 rounded-full flex items-center justify-center font-bold mb-2">2</div>
|
||||
<span className="text-sm font-medium text-gray-400">Documents</span>
|
||||
</div>
|
||||
<div className="flex-1 h-1 bg-gray-200 mx-4 -mt-6"></div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-10 h-10 bg-gray-200 text-gray-500 rounded-full flex items-center justify-center font-bold mb-2">3</div>
|
||||
<span className="text-sm font-medium text-gray-400">Insights</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="text-3xl font-black text-slate-900 mb-3">Tell us about your startup</h1>
|
||||
<p className="text-slate-500 text-lg">Help us understand your vision and business model.</p>
|
||||
</div>
|
||||
|
||||
<CardBox className="shadow-2xl border-none">
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form className="space-y-6">
|
||||
<FormField label="Startup Name" help="What is the name of your company?">
|
||||
<Field name="name" placeholder="e.g. Acme Corp" className="w-full" required />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Tagline" help="Summarize your startup in one sentence.">
|
||||
<Field name="tagline" placeholder="e.g. The future of AI-driven logistics" className="w-full" required />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Industry" help="What sector are you operating in?">
|
||||
<Field name="industry" placeholder="e.g. Fintech, Edtech, Healthtech" className="w-full" required />
|
||||
</FormField>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FormField label="Stage">
|
||||
<Field name="stage" id="stage" component="select">
|
||||
<option value="idea">Idea</option>
|
||||
<option value="mvp">MVP</option>
|
||||
<option value="beta">Beta</option>
|
||||
<option value="launched">Launched</option>
|
||||
<option value="growth">Growth</option>
|
||||
<option value="scale">Scale</option>
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Business Model">
|
||||
<Field name="business_model" id="business_model" component="select">
|
||||
<option value="saas">SaaS</option>
|
||||
<option value="marketplace">Marketplace</option>
|
||||
<option value="b2b">B2B</option>
|
||||
<option value="b2c">B2C</option>
|
||||
<option value="other">Other</option>
|
||||
</Field>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<FormField label="Description" help="A detailed description of your product or service.">
|
||||
<Field name="description" as="textarea" placeholder="Describe your startup..." className="w-full h-32" required />
|
||||
</FormField>
|
||||
|
||||
<div className="flex justify-end pt-4">
|
||||
<BaseButton
|
||||
type="submit"
|
||||
color="info"
|
||||
label="Next Step"
|
||||
icon={mdiArrowRight}
|
||||
className="px-8 py-3 rounded-xl font-bold text-lg"
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
SubmitInfoPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutGuest>
|
||||
{page}
|
||||
</LayoutGuest>
|
||||
)
|
||||
}
|
||||
|
||||
export default SubmitInfoPage
|
||||
249
frontend/src/pages/web_pages/startup-details.tsx
Normal file
249
frontend/src/pages/web_pages/startup-details.tsx
Normal file
@ -0,0 +1,249 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import axios from 'axios';
|
||||
import LayoutGuest from '../../layouts/Guest';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import UserAvatar from '../../components/UserAvatar';
|
||||
import BaseIcon from '../../components/BaseIcon';
|
||||
import {
|
||||
mdiRocketLaunch,
|
||||
mdiMapMarkerOutline,
|
||||
mdiTagOutline,
|
||||
mdiWeb,
|
||||
mdiEmailOutline,
|
||||
mdiGithub,
|
||||
mdiTrendingUp,
|
||||
mdiAccountGroup,
|
||||
mdiCurrencyUsd,
|
||||
mdiCalendarRange
|
||||
} from '@mdi/js';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
export default function StartupDetailsPage() {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
const [startup, setStartup] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (id && typeof id === 'string') {
|
||||
const fetchStartup = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`/startups/${id}`);
|
||||
setStartup(response.data);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch startup:', err);
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchStartup();
|
||||
} else if (router.isReady && !id) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [id, router.isReady]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<SectionMain>
|
||||
<div className="flex justify-center items-center min-h-64">
|
||||
<p className="text-gray-500 text-lg">Loading startup profile...</p>
|
||||
</div>
|
||||
</SectionMain>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || (!startup && !loading)) {
|
||||
return (
|
||||
<SectionMain>
|
||||
<div className="text-center py-20 bg-white rounded-xl border">
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-2">Startup Not Found</h2>
|
||||
<p className="text-gray-500 mb-6">The startup profile you're looking for doesn't exist or is no longer available.</p>
|
||||
<BaseButton label="Back to Marketplace" href="/" color="info" />
|
||||
</div>
|
||||
</SectionMain>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 min-h-screen pb-20">
|
||||
<Head>
|
||||
<title>{getPageTitle(startup.name || 'Startup Details')}</title>
|
||||
</Head>
|
||||
|
||||
{/* Header / Hero */}
|
||||
<div className="bg-white border-b py-12">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="flex flex-col md:flex-row items-center md:items-start text-center md:text-left">
|
||||
<UserAvatar
|
||||
username={startup.name}
|
||||
className="w-32 h-32 md:mr-10 mb-6 md:mb-0"
|
||||
|
||||
/>
|
||||
<div className="flex-grow">
|
||||
<div className="flex flex-col md:flex-row md:items-center mb-2">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mr-4">{startup.name}</h1>
|
||||
<span className="inline-block px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-semibold mt-2 md:mt-0 uppercase">
|
||||
{startup.stage || 'IDEA'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xl text-gray-600 mb-6 font-medium italic">
|
||||
{startup.tagline || 'Innovation in the making.'}
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center md:justify-start gap-4 text-gray-500">
|
||||
<div className="flex items-center">
|
||||
<BaseIcon path={mdiMapMarkerOutline} className="mr-1" />
|
||||
{startup.location || 'Remote'}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<BaseIcon path={mdiTagOutline} className="mr-1" />
|
||||
{startup.industry || 'Tech'}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<BaseIcon path={mdiCalendarRange} className="mr-1" />
|
||||
Founded {startup.founded_at ? new Date(startup.founded_at).getFullYear() : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 md:mt-0 flex flex-col space-y-3 w-full md:w-auto">
|
||||
<BaseButton label="Contact Founder" color="info" href="/login" className="w-full md:w-48" />
|
||||
<BaseButton label="Watch Demo" outline color="info" href={startup.demo_url || '/login'} className="w-full md:w-48" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SectionMain>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Main Content */}
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<CardBox>
|
||||
<h2 className="text-2xl font-bold mb-4">About</h2>
|
||||
<p className="text-gray-700 leading-relaxed whitespace-pre-wrap">
|
||||
{startup.description || 'No detailed description available.'}
|
||||
</p>
|
||||
</CardBox>
|
||||
|
||||
<CardBox>
|
||||
<h2 className="text-2xl font-bold mb-6">Business Metrics</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div className="flex items-start p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div className="p-3 bg-blue-100 rounded-lg mr-4">
|
||||
<BaseIcon path={mdiTrendingUp} className="text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 uppercase font-bold tracking-wider">Revenue</p>
|
||||
<p className="text-xl font-bold">${parseFloat(startup.monthly_revenue || 0).toLocaleString()}/mo</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div className="p-3 bg-green-100 rounded-lg mr-4">
|
||||
<BaseIcon path={mdiAccountGroup} className="text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 uppercase font-bold tracking-wider">Team Size</p>
|
||||
<p className="text-xl font-bold">{startup.team_size || '1-10'} members</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div className="p-3 bg-purple-100 rounded-lg mr-4">
|
||||
<BaseIcon path={mdiCurrencyUsd} className="text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 uppercase font-bold tracking-wider">Funding Stage</p>
|
||||
<p className="text-xl font-bold uppercase">{startup.funding_stage || 'Bootstrapped'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div className="p-3 bg-orange-100 rounded-lg mr-4">
|
||||
<BaseIcon path={mdiRocketLaunch} className="text-orange-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 uppercase font-bold tracking-wider">Business Model</p>
|
||||
<p className="text-xl font-bold uppercase">{startup.business_model || 'SaaS'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="space-y-6">
|
||||
<CardBox>
|
||||
<h2 className="text-xl font-bold mb-4">Quick Links</h2>
|
||||
<div className="space-y-4">
|
||||
{startup.website_url && (
|
||||
<a href={startup.website_url} target="_blank" rel="noreferrer" className="flex items-center p-3 hover:bg-gray-50 rounded-xl border transition group">
|
||||
<div className="p-2 bg-gray-100 rounded-lg mr-3 group-hover:bg-blue-50 group-hover:text-blue-600 transition">
|
||||
<BaseIcon path={mdiWeb} />
|
||||
</div>
|
||||
<span className="font-medium">Official Website</span>
|
||||
</a>
|
||||
)}
|
||||
{startup.contact_email && (
|
||||
<a href={`mailto:${startup.contact_email}`} className="flex items-center p-3 hover:bg-gray-50 rounded-xl border transition group">
|
||||
<div className="p-2 bg-gray-100 rounded-lg mr-3 group-hover:bg-blue-50 group-hover:text-blue-600 transition">
|
||||
<BaseIcon path={mdiEmailOutline} />
|
||||
</div>
|
||||
<span className="font-medium">Email Contact</span>
|
||||
</a>
|
||||
)}
|
||||
{startup.github_url && (
|
||||
<a href={startup.github_url} target="_blank" rel="noreferrer" className="flex items-center p-3 hover:bg-gray-50 rounded-xl border transition group">
|
||||
<div className="p-2 bg-gray-100 rounded-lg mr-3 group-hover:bg-blue-50 group-hover:text-blue-600 transition">
|
||||
<BaseIcon path={mdiGithub} />
|
||||
</div>
|
||||
<span className="font-medium">GitHub Repository</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className="bg-blue-50 border-blue-100">
|
||||
<h2 className="text-xl font-bold mb-4 text-blue-900">Seeking</h2>
|
||||
<div className="space-y-3">
|
||||
{startup.is_seeking_investment && (
|
||||
<div className="flex items-center p-2 px-3 bg-white border border-blue-200 rounded-lg text-blue-700 font-bold">
|
||||
<span className="mr-2">💰</span> Investment
|
||||
</div>
|
||||
)}
|
||||
{startup.is_seeking_partners && (
|
||||
<div className="flex items-center p-2 px-3 bg-white border border-blue-200 rounded-lg text-blue-700 font-bold">
|
||||
<span className="mr-2">🤝</span> Partners
|
||||
</div>
|
||||
)}
|
||||
{startup.is_hiring && (
|
||||
<div className="flex items-center p-2 px-3 bg-white border border-blue-200 rounded-lg text-blue-700 font-bold">
|
||||
<span className="mr-2">👨💻</span> Hiring
|
||||
</div>
|
||||
)}
|
||||
{!startup.is_seeking_investment && !startup.is_seeking_partners && !startup.is_hiring && (
|
||||
<p className="text-sm text-gray-500 italic">No specific needs listed at this time.</p>
|
||||
)}
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<div className="p-6 bg-white rounded-xl border text-center">
|
||||
<h3 className="font-bold mb-2">Interested in {startup.name}?</h3>
|
||||
<p className="text-sm text-gray-500 mb-4">Create an account to save this startup to your favorites and get notified about updates.</p>
|
||||
<BaseButton label="Join for Free" color="info" href="/login" className="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StartupDetailsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
1
frontend/tsconfig.tsbuildinfo
Normal file
1
frontend/tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user