diff --git a/.replit b/.replit index 535b600..a45f89c 100644 --- a/.replit +++ b/.replit @@ -14,6 +14,10 @@ run = ["npm", "run", "start"] localPort = 5000 externalPort = 80 +[[ports]] +localPort = 37531 +externalPort = 3001 + [[ports]] localPort = 43349 externalPort = 3000 diff --git a/attached_assets/logo_black_1759162686742.png b/attached_assets/logo_black_1759162686742.png new file mode 100644 index 0000000..ca36a3b Binary files /dev/null and b/attached_assets/logo_black_1759162686742.png differ diff --git a/attached_assets/logo_black_1759162717640.png b/attached_assets/logo_black_1759162717640.png new file mode 100644 index 0000000..ca36a3b Binary files /dev/null and b/attached_assets/logo_black_1759162717640.png differ diff --git a/attached_assets/스크린샷 2025-09-29 오후 10.40.47_1759162294433.png b/attached_assets/스크린샷 2025-09-29 오후 10.40.47_1759162294433.png new file mode 100644 index 0000000..459e936 Binary files /dev/null and b/attached_assets/스크린샷 2025-09-29 오후 10.40.47_1759162294433.png differ diff --git a/attached_assets/스크린샷 2025-09-29 오후 10.40.47_1759162305154.png b/attached_assets/스크린샷 2025-09-29 오후 10.40.47_1759162305154.png new file mode 100644 index 0000000..459e936 Binary files /dev/null and b/attached_assets/스크린샷 2025-09-29 오후 10.40.47_1759162305154.png differ diff --git a/attached_assets/스크린샷 2025-09-29 오후 10.41.05_1759162294433.png b/attached_assets/스크린샷 2025-09-29 오후 10.41.05_1759162294433.png new file mode 100644 index 0000000..12f910c Binary files /dev/null and b/attached_assets/스크린샷 2025-09-29 오후 10.41.05_1759162294433.png differ diff --git a/attached_assets/스크린샷 2025-09-29 오후 10.41.05_1759162305154.png b/attached_assets/스크린샷 2025-09-29 오후 10.41.05_1759162305154.png new file mode 100644 index 0000000..12f910c Binary files /dev/null and b/attached_assets/스크린샷 2025-09-29 오후 10.41.05_1759162305154.png differ diff --git a/attached_assets/스크린샷 2025-09-29 오후 10.41.14_1759162294432.png b/attached_assets/스크린샷 2025-09-29 오후 10.41.14_1759162294432.png new file mode 100644 index 0000000..1fda631 Binary files /dev/null and b/attached_assets/스크린샷 2025-09-29 오후 10.41.14_1759162294432.png differ diff --git a/attached_assets/스크린샷 2025-09-29 오후 10.41.14_1759162305154.png b/attached_assets/스크린샷 2025-09-29 오후 10.41.14_1759162305154.png new file mode 100644 index 0000000..1fda631 Binary files /dev/null and b/attached_assets/스크린샷 2025-09-29 오후 10.41.14_1759162305154.png differ diff --git a/client/src/components/CategoryTabs.tsx b/client/src/components/CategoryTabs.tsx index 845eeea..60d9dbd 100644 --- a/client/src/components/CategoryTabs.tsx +++ b/client/src/components/CategoryTabs.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Users, Hash, Building } from "lucide-react"; interface CategoryTabsProps { selectedCategory: string; @@ -6,33 +6,36 @@ interface CategoryTabsProps { } const categories = [ - { id: "People", label: "People", icon: "fas fa-users", count: 24 }, - { id: "Topics", label: "Topics", icon: "fas fa-hashtag", count: 20 }, - { id: "Companies", label: "Companies", icon: "fas fa-building", count: 27 }, + { id: "People", label: "People", Icon: Users, count: 36 }, + { id: "Topics", label: "Topics", Icon: Hash, count: 24 }, + { id: "Companies", label: "Companies", Icon: Building, count: 27 }, ]; export default function CategoryTabs({ selectedCategory, onCategoryChange }: CategoryTabsProps) { return ( -
-
-
-
- {/* Hero Section */} -
-
-

Media Intelligence & Prediction Markets

-

Access premium media outlets across People, Topics, and Companies. Participate in prediction markets and bid for exclusive editorial rights.

-
- - - - -
+
+ {/* View Mode Toggle */} +
+

Latest Articles

+
+ +
- {/* Category Tabs */} - - - {/* Media Outlets Grid */} -
+ {/* Media Outlets List */} +
{outletsLoading ? ( -
- {Array.from({ length: 8 }).map((_, i) => ( -
-
-
-
-
- ))} -
- ) : ( -
- {mediaOutlets.map((outlet) => ( - - ))} -
- )} -
- - {/* Current Auctions Section */} -
-
-

Active Media Outlet Auctions

- - - -
- - {auctionsLoading ? ( -
- {Array.from({ length: 2 }).map((_, i) => ( -
-
-
-
-
-
+ Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+
+
-
- ))} -
+
+ )) ) : ( -
- {auctions.slice(0, 2).map((auction) => ( - - ))} -
+ mediaOutlets.map((outlet) => ( + + )) )}
+ + {/* Bottom Navigation */} +
); } diff --git a/client/src/pages/Landing.tsx b/client/src/pages/Landing.tsx index 9d95f11..a84e11b 100644 --- a/client/src/pages/Landing.tsx +++ b/client/src/pages/Landing.tsx @@ -4,12 +4,13 @@ import { Input } from "@/components/ui/input"; import { Card, CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { useToast } from "@/hooks/use-toast"; -import { apiRequest } from "@/lib/queryClient"; +import { apiRequest, queryClient } from "@/lib/queryClient"; import { useMutation } from "@tanstack/react-query"; export default function Landing() { const [showLoginModal, setShowLoginModal] = useState(false); const [showRequestModal, setShowRequestModal] = useState(false); + const [loginForm, setLoginForm] = useState({ username: "", password: "" }); const [requestForm, setRequestForm] = useState({ name: "", category: "people", @@ -17,6 +18,31 @@ export default function Landing() { }); const { toast } = useToast(); + const loginMutation = useMutation({ + mutationFn: async (credentials: { username: string; password: string }) => { + return await apiRequest("POST", "/api/login", credentials); + }, + onSuccess: () => { + toast({ + title: "Success", + description: "Successfully logged in!", + }); + setShowLoginModal(false); + setLoginForm({ username: "", password: "" }); + // Refresh user data + queryClient.invalidateQueries({ queryKey: ["/api/auth/user"] }); + // Redirect to home + window.location.href = "/"; + }, + onError: () => { + toast({ + title: "Error", + description: "Invalid credentials. Please try again.", + variant: "destructive" + }); + } + }); + const requestMutation = useMutation({ mutationFn: async (data: typeof requestForm) => { await apiRequest("POST", "/api/media-outlet-requests", data); @@ -39,7 +65,12 @@ export default function Landing() { }); const handleLogin = () => { - window.location.href = "/api/login"; + setShowLoginModal(true); + }; + + const handleLoginSubmit = (e: React.FormEvent) => { + e.preventDefault(); + loginMutation.mutate(loginForm); }; const handleRequestSubmit = (e: React.FormEvent) => { @@ -54,10 +85,11 @@ export default function Landing() {
-
- S -
- SAPIENS + SAPIENS
@@ -158,6 +190,62 @@ export default function Landing() {
+ {/* Login Modal */} + + + + Login to SAPIENS + +
+
+ + setLoginForm(prev => ({ ...prev, username: e.target.value }))} + placeholder="Enter username" + required + data-testid="input-username" + /> +
+ +
+ + setLoginForm(prev => ({ ...prev, password: e.target.value }))} + placeholder="Enter password" + required + data-testid="input-password" + /> +
+ +
+ Use: admin/1234 or superadmin/1234 +
+ +
+ + +
+
+
+
+ {/* Request Modal */} diff --git a/server/index.ts b/server/index.ts index 8bf1912..45f98c9 100644 --- a/server/index.ts +++ b/server/index.ts @@ -39,6 +39,9 @@ app.use((req, res, next) => { (async () => { const server = await registerRoutes(app); + // Serve attached assets statically + app.use('/attached_assets', express.static('attached_assets')); + app.use((err: any, _req: Request, res: Response, _next: NextFunction) => { const status = err.status || err.statusCode || 500; const message = err.message || "Internal Server Error"; diff --git a/server/routes.ts b/server/routes.ts index 6cf7737..9e4f39c 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1,7 +1,7 @@ import type { Express } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; -import { setupAuth, isAuthenticated } from "./replitAuth"; +import { setupAuth, isAuthenticated } from "./simpleAuth"; import { insertArticleSchema, insertMediaOutletRequestSchema, insertBidSchema, insertCommentSchema } from "@shared/schema"; export async function registerRoutes(app: Express): Promise { @@ -11,8 +11,7 @@ export async function registerRoutes(app: Express): Promise { // Auth routes app.get('/api/auth/user', isAuthenticated, async (req: any, res) => { try { - const userId = req.user.claims.sub; - const user = await storage.getUser(userId); + const user = req.user; res.json(user); } catch (error) { console.error("Error fetching user:", error); @@ -82,8 +81,7 @@ export async function registerRoutes(app: Express): Promise { app.post('/api/articles', isAuthenticated, async (req: any, res) => { try { - const userId = req.user.claims.sub; - const user = await storage.getUser(userId); + const user = req.user; if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) { return res.status(403).json({ message: "Insufficient permissions" }); @@ -91,7 +89,7 @@ export async function registerRoutes(app: Express): Promise { const articleData = insertArticleSchema.parse({ ...req.body, - authorId: userId + authorId: user.id }); const article = await storage.createArticle(articleData); diff --git a/server/simpleAuth.ts b/server/simpleAuth.ts new file mode 100644 index 0000000..c0c3f7b --- /dev/null +++ b/server/simpleAuth.ts @@ -0,0 +1,89 @@ +import type { Express, RequestHandler } from "express"; +import session from "express-session"; +import connectPg from "connect-pg-simple"; + +// Hardcoded credentials +const CREDENTIALS = { + admin: { password: "1234", role: "admin", firstName: "Admin", lastName: "User" }, + superadmin: { password: "1234", role: "superadmin", firstName: "Super", lastName: "Admin" } +}; + +export function getSession() { + const sessionTtl = 7 * 24 * 60 * 60 * 1000; // 1 week + const pgStore = connectPg(session); + const sessionStore = new pgStore({ + conString: process.env.DATABASE_URL, + createTableIfMissing: false, + ttl: sessionTtl, + tableName: "sessions", + }); + return session({ + secret: process.env.SESSION_SECRET!, + store: sessionStore, + resave: false, + saveUninitialized: false, + cookie: { + httpOnly: true, + secure: false, // Changed to false for development + maxAge: sessionTtl, + }, + }); +} + +export async function setupAuth(app: Express) { + app.set("trust proxy", 1); + app.use(getSession()); + + // Login route + app.post("/api/login", (req, res) => { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ message: "Username and password required" }); + } + + const user = CREDENTIALS[username as keyof typeof CREDENTIALS]; + if (!user || user.password !== password) { + return res.status(401).json({ message: "Invalid credentials" }); + } + + // Set session + (req.session as any).user = { + id: username, + email: `${username}@sapiens.com`, + firstName: user.firstName, + lastName: user.lastName, + role: user.role, + isAuthenticated: true + }; + + res.json({ + id: username, + email: `${username}@sapiens.com`, + firstName: user.firstName, + lastName: user.lastName, + role: user.role + }); + }); + + // Logout route + app.post("/api/logout", (req, res) => { + req.session.destroy((err) => { + if (err) { + return res.status(500).json({ message: "Could not log out" }); + } + res.json({ message: "Logged out successfully" }); + }); + }); +} + +export const isAuthenticated: RequestHandler = (req, res, next) => { + const user = (req.session as any)?.user; + + if (!user || !user.isAuthenticated) { + return res.status(401).json({ message: "Unauthorized" }); + } + + req.user = user; + next(); +}; \ No newline at end of file