refactor: migrate to remix
This commit is contained in:
parent
f6e627e045
commit
2d339f2ebe
|
@ -3,9 +3,9 @@
|
|||
import { formatDate } from "@/utils";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Article } from "../lib/prisma.ts";
|
||||
import { Article } from "@/lib/prisma";
|
||||
import useSWR from "swr";
|
||||
import { ApiResponse } from "../lib/feed.ts";
|
||||
import { ApiResponse } from "@/lib/feed";
|
||||
|
||||
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
||||
|
||||
|
@ -42,7 +42,7 @@ const Feed = ({
|
|||
currentPage === 0
|
||||
? { data: initialData, current: 0, next: 5 }
|
||||
: undefined, // Use initialData only for the first page
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
|
@ -0,0 +1,20 @@
|
|||
import { Link } from "@remix-run/react";
|
||||
import React from "react";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Footer = ({}: Props) => {
|
||||
return (
|
||||
<div className="w-full mt-5 flex flex-row items-center justify-center text-gray-400">
|
||||
<Link to="/about" className="hover:text-white hover:underline">
|
||||
About Web3.news
|
||||
</Link>
|
||||
<div className="h-7 w-[1px] bg-current mx-4" />
|
||||
<Link to="/donate" className="hover:text-white hover:underline">
|
||||
Contribute to the cause
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
|
@ -1,21 +1,22 @@
|
|||
import React from "react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import React from "react";
|
||||
import { Link } from "@remix-run/react";
|
||||
|
||||
type Props = {}
|
||||
import Logo from "@/images/lume-logo-sm.png";
|
||||
|
||||
type Props = {};
|
||||
|
||||
export const Header = ({}: Props) => {
|
||||
return (
|
||||
<header className="w-full flex flex-row justify-between relative">
|
||||
<div className="flex flex-col">
|
||||
<Link href="/">
|
||||
<Link to="/">
|
||||
<Web3NewsLogo />
|
||||
<div className="relative mt-1">
|
||||
<Image
|
||||
<image
|
||||
className="-right-8 -top-3 absolute"
|
||||
width={28}
|
||||
height={24}
|
||||
src="/lume-logo-sm.png"
|
||||
src={Logo}
|
||||
alt=""
|
||||
/>
|
||||
<span className="right-0 -top-[6px] absolute text-white text-opacity-50 text-sm font-normal font-secondary leading-7">
|
||||
|
@ -26,21 +27,21 @@ export const Header = ({}: Props) => {
|
|||
</div>
|
||||
<div className="flex gap-3 font-normal flex-row text-gray-300 rounded">
|
||||
<Link
|
||||
href="/about"
|
||||
to="/about"
|
||||
className="hover:text-white p-2 px-4 hover:bg-gray-800 rounded"
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
<Link
|
||||
href="/donate"
|
||||
to="/donate"
|
||||
className="hover:text-white p-2 px-4 hover:bg-gray-800 rounded"
|
||||
>
|
||||
Contribute
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const Web3NewsLogo = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
|
@ -61,7 +62,7 @@ const Web3NewsLogo = ({ className }: { className?: string }) => {
|
|||
fill="#ACF9C0"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Header
|
||||
export default Header;
|
|
@ -1,76 +1,71 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import React, {
|
||||
FormEvent,
|
||||
type FormEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react"
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline" // Assuming usage of Heroicons for icons
|
||||
import { flushSync } from "react-dom"
|
||||
import Link from "next/link"
|
||||
import { usePathname, useSearchParams, useRouter } from "next/navigation"
|
||||
import { FILTER_TIMES, formatDate, getResults } from "@/utils"
|
||||
useState,
|
||||
} from "react";
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; // Assuming usage of Heroicons for icons
|
||||
import { flushSync } from "react-dom";
|
||||
import {
|
||||
Link,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useSearchParams,
|
||||
} from "@remix-run/react";
|
||||
import { FILTER_TIMES, formatDate, getResults } from "@/utils";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "./ui/select"
|
||||
import { SitesCombobox } from "./SitesCombobox"
|
||||
SelectValue,
|
||||
} from "./ui/select";
|
||||
import { SitesCombobox } from "./SitesCombobox";
|
||||
|
||||
type Props = {}
|
||||
type Props = {};
|
||||
|
||||
const SearchBar = ({}: Props) => {
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [query, setQuery] = useState(searchParams?.get("q") ?? "")
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [activeInput, setActiveInput] = useState(true)
|
||||
const [dirtyInput, setDirtyInput] = useState(false)
|
||||
const [results, setResults] = useState<SearchResult[]>([])
|
||||
const SearchBar = () => {
|
||||
let navigate = useNavigate();
|
||||
let { pathname } = useLocation();
|
||||
let [searchParams] = useSearchParams();
|
||||
const [query, setQuery] = useState(searchParams.get("q") ?? "");
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeInput, setActiveInput] = useState(true);
|
||||
const [dirtyInput, setDirtyInput] = useState(false);
|
||||
const [results, setResults] = useState<SearchResult[]>([]);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
async (event?: FormEvent<HTMLFormElement>) => {
|
||||
event?.preventDefault()
|
||||
setIsLoading(true)
|
||||
const newSearchParams = new URLSearchParams(searchParams ?? undefined)
|
||||
async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setIsLoading(true);
|
||||
let newSearchParams = new URLSearchParams(searchParams.toString());
|
||||
|
||||
if (query) {
|
||||
newSearchParams.set("q", query)
|
||||
newSearchParams.set("q", query);
|
||||
} else {
|
||||
newSearchParams.delete("q")
|
||||
newSearchParams.delete("q");
|
||||
}
|
||||
|
||||
router.push(`${pathname}?${newSearchParams}`)
|
||||
navigate(`${pathname}?${newSearchParams.toString()}`);
|
||||
|
||||
// Perform search and update results state
|
||||
// Assume fetchResults is a function that fetches search results
|
||||
// const searchResults = await fetchResults(query);
|
||||
// Mock the search results
|
||||
const searchResults = await getResults({ query })
|
||||
const searchResults = await getResults({ query });
|
||||
|
||||
setResults(searchResults)
|
||||
setIsLoading(false)
|
||||
setActiveInput(false)
|
||||
setResults(searchResults);
|
||||
setIsLoading(false);
|
||||
setActiveInput(false);
|
||||
},
|
||||
[
|
||||
query,
|
||||
setResults,
|
||||
setIsLoading,
|
||||
setActiveInput,
|
||||
searchParams,
|
||||
router,
|
||||
pathname
|
||||
]
|
||||
)
|
||||
[query, searchParams, navigate, pathname]
|
||||
);
|
||||
|
||||
const isActive = results.length > 0 || dirtyInput
|
||||
const isActive = results.length > 0 || dirtyInput;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -96,7 +91,7 @@ const SearchBar = ({}: Props) => {
|
|||
<input
|
||||
ref={(element) => {
|
||||
if (element) {
|
||||
inputRef.current = element
|
||||
inputRef.current = element;
|
||||
}
|
||||
}}
|
||||
className={`flex-grow inline bg-transparent text-white placeholder-gray-400 outline-none ring-none ${
|
||||
|
@ -114,15 +109,15 @@ const SearchBar = ({}: Props) => {
|
|||
style={
|
||||
query
|
||||
? {
|
||||
width: `calc(${query.length}ch+2px)`
|
||||
width: `calc(${query.length}ch+2px)`,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (!dirtyInput) {
|
||||
setDirtyInput(true)
|
||||
setDirtyInput(true);
|
||||
}
|
||||
setQuery(e.target.value)
|
||||
setQuery(e.target.value);
|
||||
}}
|
||||
/>
|
||||
{isActive ? (
|
||||
|
@ -138,9 +133,9 @@ const SearchBar = ({}: Props) => {
|
|||
className="block w-full flex-1 text-blue-300"
|
||||
onClick={() => {
|
||||
flushSync(() => {
|
||||
setActiveInput(true)
|
||||
})
|
||||
inputRef.current?.focus()
|
||||
setActiveInput(true);
|
||||
});
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
{'"'}
|
||||
|
@ -156,13 +151,18 @@ const SearchBar = ({}: Props) => {
|
|||
{/* Dropdown component should be here */}
|
||||
<SitesCombobox />
|
||||
{/* Dropdown component should be here */}
|
||||
<Select defaultValue={'0'}>
|
||||
<Select defaultValue={"0"}>
|
||||
<SelectTrigger className="hover:bg-muted w-auto">
|
||||
<SelectValue placeholder="Time ago" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{FILTER_TIMES.map((v) => (
|
||||
<SelectItem value={String(v.value)} key={`FilteTimeSelectItem_${v.value}`}>{v.label}</SelectItem>
|
||||
<SelectItem
|
||||
value={String(v.value)}
|
||||
key={`FilteTimeSelectItem_${v.value}`}
|
||||
>
|
||||
{v.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
@ -175,7 +175,7 @@ const SearchBar = ({}: Props) => {
|
|||
<hr className="my-4 border-1" />
|
||||
{results.map((item) => (
|
||||
<Link
|
||||
href={`/article/${item.slug}`}
|
||||
to={`/article/${item.slug}`}
|
||||
key={item.id}
|
||||
className="flex cursor-pointer flex-row items-center space-x-5 my-2 py-2 px-4 hover:bg-gray-800 rounded-md"
|
||||
>
|
||||
|
@ -185,7 +185,7 @@ const SearchBar = ({}: Props) => {
|
|||
<h3 className="text-md font-semibold text-white">{item.title}</h3>
|
||||
</Link>
|
||||
))}
|
||||
<Link href={`/search?q=${encodeURIComponent(query)}`}>
|
||||
<Link to={`/search?q=${encodeURIComponent(query)}`}>
|
||||
<button className="mt-4 flex justify-center items-center bg-secondary w-full py-7 text-white hover:bg-teal-800 transition-colors">
|
||||
{results.length}+ search results for{" "}
|
||||
<span className="text-blue-300 ml-1">{query}</span>
|
||||
|
@ -195,13 +195,13 @@ const SearchBar = ({}: Props) => {
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Placeholder components for Shadcn
|
||||
const LoadingComponent = () => {
|
||||
// Replace with actual Shadcn Loading component
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
return <div>Loading...</div>;
|
||||
};
|
||||
|
||||
export default SearchBar
|
||||
export default SearchBar;
|
|
@ -1,45 +1,50 @@
|
|||
"use client"
|
||||
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||
import React, { FormEvent, useState } from "react"
|
||||
import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "./ui/select"
|
||||
import { SitesCombobox } from "./SitesCombobox"
|
||||
import { FILTER_TIMES } from "@/utils"
|
||||
import React, { FormEvent, useState } from "react";
|
||||
import { useLocation, useNavigate } from "@remix-run/react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectTrigger,
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
} from "./ui/select";
|
||||
import { SitesCombobox } from "./SitesCombobox";
|
||||
import { FILTER_TIMES } from "@/utils";
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
placeholder?: string
|
||||
className?: string
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
filters?: {
|
||||
sites: { value: string; label: string }[]
|
||||
}
|
||||
}
|
||||
sites: { value: string; label: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
const SimplifiedSearchBar = ({
|
||||
value: initialValue,
|
||||
placeholder,
|
||||
filters,
|
||||
className
|
||||
className,
|
||||
}: Props) => {
|
||||
const searchParams = useSearchParams()
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const [value, setValue] = useState<string>(initialValue)
|
||||
let navigate = useNavigate();
|
||||
let location = useLocation();
|
||||
const [value, setValue] = useState<string>(initialValue);
|
||||
|
||||
const handleSearch = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
const newSearchParams = new URLSearchParams(searchParams ?? undefined)
|
||||
event.preventDefault();
|
||||
const newSearchParams = new URLSearchParams(location.search);
|
||||
|
||||
if (value) {
|
||||
newSearchParams.set("q", value)
|
||||
newSearchParams.set("q", value);
|
||||
} else {
|
||||
newSearchParams.delete("q")
|
||||
newSearchParams.delete("q");
|
||||
}
|
||||
|
||||
router.push(`${pathname}?${newSearchParams}`)
|
||||
}
|
||||
navigate(`${location.pathname}?${newSearchParams}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
className={`flex items-center text-lg border-b border-primary pb-2`}
|
||||
className={`flex items-center text-lg border-b border-primary pb-2 ${className}`}
|
||||
onSubmit={handleSearch}
|
||||
>
|
||||
<div className="flex-1 flex flex-row max-w-full">
|
||||
|
@ -83,7 +88,7 @@ const SimplifiedSearchBar = ({
|
|||
</Select>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SimplifiedSearchBar
|
||||
export default SimplifiedSearchBar;
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/utils"
|
||||
import { cn } from "@/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
|
@ -31,26 +31,26 @@ const buttonVariants = cva(
|
|||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants }
|
||||
export { Button, buttonVariants };
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -1,4 +1,4 @@
|
|||
import prisma, { Article } from "../lib/prisma.ts";
|
||||
import prisma, { Article } from "@/lib/prisma";
|
||||
export type ApiResponse<T = Record<string, any>> = {
|
||||
data: T[];
|
||||
current: number;
|
|
@ -0,0 +1,51 @@
|
|||
import { LinksFunction, MetaFunction } from "@remix-run/node";
|
||||
import {
|
||||
Links,
|
||||
LiveReload,
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
} from "@remix-run/react";
|
||||
import Header from "@/components/LayoutHeader"; // Adjust the import path as needed
|
||||
import Footer from "@/components/LayoutFooter"; // Adjust the import path as needed
|
||||
import globalStyles from "./styles/global.css";
|
||||
import { cssBundleHref } from "@remix-run/css-bundle"; // Adjust the import path as needed
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{ rel: "stylesheet", href: globalStyles },
|
||||
|
||||
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
|
||||
// Add your Google font links here
|
||||
// Example: { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400&display=swap" },
|
||||
// Example: { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Jaldi:wght@400&display=swap" },
|
||||
];
|
||||
|
||||
export const meta: MetaFunction = () => [
|
||||
{
|
||||
charset: "utf-8",
|
||||
},
|
||||
{ viewport: "width=device-width,initial-scale=1" },
|
||||
];
|
||||
|
||||
export default function Root() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body className="font-main bg-gray-900 flex">
|
||||
<main className="dark flex w-full min-h-screen flex-col md:px-40 items-center py-16 mx-auto">
|
||||
<Header />
|
||||
<Outlet />
|
||||
<Footer />
|
||||
</main>
|
||||
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
<LiveReload />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import { ArrowIcon } from "@/components/ArrowIcon"
|
||||
import * as GraphicSection from "@/components/GraphicSection"
|
||||
import { ArrowIcon } from "@/components/ArrowIcon";
|
||||
import * as GraphicSection from "@/components/GraphicSection";
|
||||
|
||||
import Logo from "@/images/lume-logo-bg.png";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
|
@ -40,7 +42,7 @@ export default function Page() {
|
|||
>
|
||||
<GraphicSection.Background>
|
||||
<img
|
||||
src="/lume-logo-bg.png"
|
||||
src={Logo}
|
||||
className="background opacity-50 transition-transform duration-500 transform-gpu absolute -top-[100px] -left-10"
|
||||
alt=""
|
||||
aria-hidden
|
||||
|
@ -60,5 +62,5 @@ export default function Page() {
|
|||
</GraphicSection.Foreground>
|
||||
</GraphicSection.Root>
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Link from "next/link";
|
||||
import { Link } from "@remix-run/react";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
|
@ -34,7 +34,7 @@ export default function Page() {
|
|||
|
||||
<p>So help us in our goals to level-up Web3.</p>
|
||||
|
||||
<Link href="https://gitcoin.com">
|
||||
<Link to="https://gitcoin.com">
|
||||
<button
|
||||
className={`my-6 p-8 text-gray-500 bg-gray-800 hover:bg-gray-800/70`}
|
||||
>
|
||||
|
@ -42,5 +42,5 @@ export default function Page() {
|
|||
</button>
|
||||
</Link>
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -1,29 +1,27 @@
|
|||
"use client"
|
||||
|
||||
import { ArrowLeftIcon } from "@radix-ui/react-icons"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ArrowLeftIcon } from "@radix-ui/react-icons";
|
||||
import { Link, Outlet, useLocation } from "@remix-run/react";
|
||||
|
||||
const TEXT_DICT = {
|
||||
"/about": {
|
||||
headline: "Sharing community news on the open, user-owned web you deserve.",
|
||||
tagline: "Learn about our community"
|
||||
tagline: "Learn about our community",
|
||||
},
|
||||
"/donate": {
|
||||
headline: "We think people should have free access to information no matter how they choose to access it.",
|
||||
tagline: "Help us break the pattern"
|
||||
}
|
||||
}
|
||||
headline:
|
||||
"We think people should have free access to information no matter how they choose to access it.",
|
||||
tagline: "Help us break the pattern",
|
||||
},
|
||||
};
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const text = TEXT_DICT[pathname! as '/about' | '/donate']
|
||||
export default function Layout() {
|
||||
const { pathname } = useLocation();
|
||||
const text = TEXT_DICT[pathname! as "/about" | "/donate"];
|
||||
return (
|
||||
<section className="w-full">
|
||||
<header className="text-white mt-10 pb-3 border-b-2 border-primary">
|
||||
<h2>{text.headline}</h2>
|
||||
</header>
|
||||
<Link href="/">
|
||||
<Link to="/">
|
||||
<button className="my-4 -ml-3 px-3 py-2 text-gray-400 hover:bg-gray-800 hover:text-white rounded">
|
||||
<ArrowLeftIcon className="w-4 h-4 inline mr-2 -mt-1" />
|
||||
Back to Home
|
||||
|
@ -39,16 +37,18 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||
</ol>
|
||||
</nav>
|
||||
</aside>
|
||||
<section className="w-full">{children}</section>
|
||||
<section className="w-full">
|
||||
<Outlet />
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const AsideItem = ({ title, href }: { title: string; href: string }) => {
|
||||
const pathname = usePathname()
|
||||
const { pathname } = useLocation();
|
||||
return (
|
||||
<Link href={href}>
|
||||
<Link to={href}>
|
||||
<li>
|
||||
<button
|
||||
className={`w-[calc(100%-20px)] mb-3 p-8 text-gray-500 bg-gray-800 text-start ${
|
||||
|
@ -61,5 +61,5 @@ const AsideItem = ({ title, href }: { title: string; href: string }) => {
|
|||
</button>
|
||||
</li>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
|
@ -1,34 +1,37 @@
|
|||
import Feed from "@/components/Feed";
|
||||
import SearchBar from "@/components/SearchBar";
|
||||
import { ApiResponse, fetchFeedData } from "@/lib/feed.ts";
|
||||
import { ApiResponse, fetchFeedData } from "@/lib/feed";
|
||||
import * as GraphicSection from "@/components/GraphicSection";
|
||||
import { ArrowIcon } from "@/components/ArrowIcon";
|
||||
import { GetServerSideProps } from "next";
|
||||
import { Article } from "@/lib/prisma.ts";
|
||||
import { Article } from "@/lib/prisma";
|
||||
|
||||
type Props = {
|
||||
import Logo from "@/images/lume-logo-bg.png";
|
||||
import { json, LoaderFunction, redirect } from "@remix-run/node";
|
||||
import { useLoaderData } from "@remix-run/react";
|
||||
|
||||
type LoaderData = {
|
||||
data: ApiResponse<Article>;
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async ({
|
||||
req,
|
||||
params,
|
||||
}) => {
|
||||
if (!req.headers.referer && params?.q) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `/search?q=${params?.q}`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
export let loader: LoaderFunction = async ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const referer = request.headers.get("referer");
|
||||
const queryParam = url.searchParams.get("q");
|
||||
|
||||
// Handle redirection based on referer and query parameters
|
||||
if (!referer && queryParam) {
|
||||
return redirect(`/search?q=${queryParam}`);
|
||||
}
|
||||
|
||||
// Fetch your data here
|
||||
const data = await fetchFeedData({});
|
||||
|
||||
return { props: { data } };
|
||||
// Return the fetched data as JSON
|
||||
return json({ data });
|
||||
};
|
||||
|
||||
export default async function Home({ data }: Props) {
|
||||
export default function Index() {
|
||||
let { data } = useLoaderData<LoaderData>();
|
||||
return (
|
||||
<>
|
||||
<SearchBar />
|
||||
|
@ -47,7 +50,7 @@ export default async function Home({ data }: Props) {
|
|||
>
|
||||
<GraphicSection.Background>
|
||||
<img
|
||||
src="/lume-logo-bg.png"
|
||||
src={Logo}
|
||||
className="background transition-transform duration-500 transform-gpu absolute -top-[320px] -right-10"
|
||||
alt=""
|
||||
aria-hidden
|
|
@ -1,11 +1,17 @@
|
|||
import { fetchFeedData } from "@/lib/feed.ts";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { json, LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { fetchFeedData } from "@/lib/feed";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { filter, page = "0" } = req.nextUrl.searchParams as any as {
|
||||
filter: "latest" | "day" | "week" | "month";
|
||||
type Filter = "latest" | "day" | "week" | "month";
|
||||
|
||||
export async function loader({ params }: LoaderFunctionArgs) {
|
||||
let filter: Filter | null = null;
|
||||
let page = "0";
|
||||
if (params?.searchParams) {
|
||||
({ filter, page = "0" } = params.searchParams as any as {
|
||||
filter: Filter;
|
||||
page: string;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Define the limit of articles per page
|
||||
|
@ -26,8 +32,10 @@ export async function GET(req: NextRequest) {
|
|||
// Fetch data using the fetchFeedData function
|
||||
const dataResponse = await fetchFeedData(queryParams);
|
||||
|
||||
return NextResponse.json(dataResponse);
|
||||
return json(dataResponse);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Internal Server Error" });
|
||||
throw new Response("Internal Server Error", {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,21 +1,14 @@
|
|||
"use client"
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline"
|
||||
import Link from "next/link"
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
// page https://www.businessinsider.com/web3-music-platforms-blockchain-even-sound-iyk-streaming-services-artists-2023-9#:~:text=Web3%20music%20platforms%20are%20combining,and%20'create%20dope%20consumer%20experiences'&text=Web3%20music%20platforms%20are%20changing,and%20ways%20to%20reach%20fans.
|
||||
import React from "react"
|
||||
import React from "react";
|
||||
import { Link } from "@remix-run/react";
|
||||
|
||||
type Props = {
|
||||
params: {
|
||||
slug: string
|
||||
}
|
||||
}
|
||||
|
||||
const Page = ({ params }: Props) => {
|
||||
const Page = () => {
|
||||
// TODO: Explore based on the slug, we can also change the slug to be like the id or something the backend understands
|
||||
// We can also pre-render the article from the backend
|
||||
return (
|
||||
<>
|
||||
<Link href="/" className="w-full mt-1">
|
||||
<Link to="/" className="w-full mt-1">
|
||||
<button className="px-3 py-2 text-gray-400 hover:bg-gray-800 hover:text-white rounded">
|
||||
<ArrowLeftIcon className="w-4 h-4 inline mr-2 -mt-1" />
|
||||
Go Back Home
|
||||
|
@ -28,7 +21,7 @@ const Page = ({ params }: Props) => {
|
|||
></iframe>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Page
|
||||
export default Page;
|
|
@ -1,18 +1,13 @@
|
|||
import React, { FormEvent } from "react"
|
||||
import Link from "next/link"
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline"
|
||||
import { formatDate, getResults } from "@/utils"
|
||||
import SimplifiedSearchBar from "@/components/SimplifiedSearchBar"
|
||||
import React from "react";
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { formatDate, getResults } from "@/utils";
|
||||
import SimplifiedSearchBar from "@/components/SimplifiedSearchBar";
|
||||
import { Link, useSearchParams } from "@remix-run/react";
|
||||
|
||||
type Props = {
|
||||
searchParams: {
|
||||
q?: string
|
||||
}
|
||||
}
|
||||
|
||||
const Page = async ({ searchParams }: Props) => {
|
||||
const query = searchParams.q ?? ""
|
||||
const results = await getResults({ query: searchParams.q })
|
||||
const Page = async () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const query = searchParams.get("q") ?? "";
|
||||
const results = await getResults({ query: query });
|
||||
|
||||
return (
|
||||
<div className="w-full items-center text-lg">
|
||||
|
@ -23,7 +18,7 @@ const Page = async ({ searchParams }: Props) => {
|
|||
}
|
||||
/>
|
||||
|
||||
<Link href="/">
|
||||
<Link to="/">
|
||||
<button className="my-4 -ml-3 px-3 py-2 text-gray-400 hover:bg-gray-800 hover:text-white rounded">
|
||||
<ArrowLeftIcon className="w-4 h-4 inline mr-2 -mt-1" />
|
||||
Go Back Home
|
||||
|
@ -34,7 +29,7 @@ const Page = async ({ searchParams }: Props) => {
|
|||
<>
|
||||
{results.map((item) => (
|
||||
<Link
|
||||
href={`/article/${item.slug}`}
|
||||
to={`/article/${item.slug}`}
|
||||
key={item.id}
|
||||
className="flex cursor-pointer flex-row items-center space-x-5 my-2 py-2 px-4 hover:bg-gray-800 rounded-md"
|
||||
>
|
||||
|
@ -44,7 +39,7 @@ const Page = async ({ searchParams }: Props) => {
|
|||
<h3 className="text-md font-semibold text-white">{item.title}</h3>
|
||||
</Link>
|
||||
))}
|
||||
<Link href={`/search?q=${encodeURIComponent(query)}`}>
|
||||
<Link to={`/search?q=${encodeURIComponent(query)}`}>
|
||||
<button className="rounded mt-4 flex justify-center items-center bg-gray-800 mx-auto w-44 py-7 text-white hover:bg-gray-800/50 transition-colors">
|
||||
Load More
|
||||
</button>
|
||||
|
@ -52,7 +47,7 @@ const Page = async ({ searchParams }: Props) => {
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Page
|
||||
export default Page;
|
|
@ -1,4 +0,0 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
|
||||
module.exports = nextConfig
|
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
|
@ -2,14 +2,20 @@
|
|||
"name": "web3.news",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"prisma": {
|
||||
"seed": "ts-node-esm prisma/seed.mts"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"build": "run-s \"build:*\"",
|
||||
"build:css": "npm run generate:css -- --style=compressed",
|
||||
"build:remix": "remix build",
|
||||
"dev": "run-p \"dev:*\"",
|
||||
"dev:css": "npm run generate:css -- --watch",
|
||||
"dev:remix": "remix dev",
|
||||
"generate:css": "sass styles/:app/styles/",
|
||||
"start": "remix-serve build",
|
||||
"typecheck": "tsc",
|
||||
"bridge": "ts-node-esm bridge.mts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -22,14 +28,18 @@
|
|||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@remix-run/css-bundle": "^2.4.0",
|
||||
"@remix-run/node": "^2.4.0",
|
||||
"@remix-run/react": "^2.4.0",
|
||||
"@remix-run/serve": "^2.4.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cmdk": "^0.2.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"next": "14.0.2",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"isbot": "^3.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"slugify": "^1.6.6",
|
||||
"swr": "^2.2.4",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
|
@ -37,6 +47,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"@remix-run/dev": "^2.4.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
|
@ -44,11 +55,13 @@
|
|||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8",
|
||||
"prisma": "^5.6.0",
|
||||
"sass": "^1.69.5",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5"
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('@remix-run/dev').AppConfig} */
|
||||
export default {
|
||||
ignoredRouteFiles: ["**/.*"],
|
||||
// appDirectory: "app",
|
||||
// assetsBuildDirectory: "public/build",
|
||||
// publicPath: "/build/",
|
||||
// serverBuildPath: "build/index.js",
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="@remix-run/dev" />
|
||||
/// <reference types="@remix-run/node" />
|
|
@ -1,89 +0,0 @@
|
|||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { S5Client } from "@lumeweb/s5-js";
|
||||
import xml2js from "xml2js";
|
||||
import prisma from "../../../../lib/prisma.ts";
|
||||
import * as cheerio from "cheerio";
|
||||
import slugify from "slugify";
|
||||
import path from "path";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const client = new S5Client("https://s5.web3portal.com");
|
||||
const data = await req.json();
|
||||
const meta = (await client.getMetadata(data.cid)) as any;
|
||||
const fileMeta = meta.metadata as any;
|
||||
|
||||
const paths = fileMeta.paths as {
|
||||
[file: string]: {
|
||||
cid: string;
|
||||
};
|
||||
};
|
||||
if (!("sitemap.xml" in paths)) {
|
||||
return NextResponse.error();
|
||||
}
|
||||
|
||||
const sitemapData = await client.downloadData(paths["sitemap.xml"].cid);
|
||||
const sitemap = await xml2js.parseStringPromise(sitemapData);
|
||||
|
||||
const urls = sitemap.urlset.url.map((urlEntry: any) => {
|
||||
const url = urlEntry.loc[0];
|
||||
let pathname = new URL(url).pathname;
|
||||
|
||||
// Normalize and remove leading and trailing slashes from the path
|
||||
pathname = path.normalize(pathname).replace(/^\/|\/$/g, "");
|
||||
|
||||
// Function to determine if a URL path represents a directory
|
||||
const isDirectory = (pathname: string) => {
|
||||
// Check if the path directly maps to a file in the paths object
|
||||
return !paths.hasOwnProperty(pathname);
|
||||
};
|
||||
|
||||
// Check if the path is a directory and look for a directory index
|
||||
if (isDirectory(pathname)) {
|
||||
for (const file of fileMeta.tryFiles) {
|
||||
const indexPath = path.join(pathname, file);
|
||||
if (paths.hasOwnProperty(indexPath)) {
|
||||
pathname = indexPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch cid after confirming the final path
|
||||
const cid = paths[pathname]?.cid;
|
||||
|
||||
return { url, cid, path: pathname }; // including cid in return object after final path is determined
|
||||
});
|
||||
|
||||
for (const { url, cid } of urls) {
|
||||
if (cid) {
|
||||
const exists = await prisma.article.findUnique({
|
||||
where: { cid },
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
// Fetch and parse the content using CID
|
||||
const contentData = Buffer.from(
|
||||
await client.downloadData(cid),
|
||||
).toString();
|
||||
|
||||
const $ = cheerio.load(contentData);
|
||||
const title = $("title").text(); // Extract the title from the content
|
||||
|
||||
const record = {
|
||||
title,
|
||||
url,
|
||||
cid: cid,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
slug: slugify(new URL(url).pathname),
|
||||
siteKey: slugify(data.site as string),
|
||||
};
|
||||
|
||||
// Insert a new record into the database
|
||||
await prisma.article.create({
|
||||
data: record,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
|
@ -1,76 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 248, 45%, 7%;
|
||||
--popover-foreground: 0, 0%, 100%;
|
||||
|
||||
--primary: 136, 87%, 83%;
|
||||
--primary-foreground: black;
|
||||
|
||||
--secondary: 169 46% 37%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 0 0% 32%;
|
||||
--muted-foreground: 0 0% 32%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 0 0% 32%;
|
||||
--input: 0 0% 32%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 248, 45%, 7%;
|
||||
--popover-foreground: 0, 0%, 100%;
|
||||
|
||||
--primary: 136, 87%, 83%;
|
||||
--primary-foreground: black;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground font-main;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import type { Metadata } from "next"
|
||||
import { Jaldi, Be_Vietnam_Pro } from "next/font/google"
|
||||
import Header from "@/components/LayoutHeader"
|
||||
import Footer from "@/components/LayoutFooter"
|
||||
import "./globals.css"
|
||||
|
||||
const beVietnamPro = Be_Vietnam_Pro({ weight: ["400"], subsets: ["latin"], variable: "--font-be-vietnam-pro" })
|
||||
const jaldi = Jaldi({
|
||||
subsets: ["latin"],
|
||||
weight: ["400"],
|
||||
variable: "--font-jaldi"
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app"
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`font-main bg-gray-900 flex ${beVietnamPro.variable} ${jaldi.variable}`}>
|
||||
<main className="dark flex w-full min-h-screen flex-col md:px-40 items-center py-16 mx-auto">
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import Link from "next/link"
|
||||
import React from "react"
|
||||
|
||||
type Props = {}
|
||||
|
||||
const Footer = ({}: Props) => {
|
||||
return (<div className="w-full mt-5 flex flex-row items-center justify-center text-gray-400">
|
||||
<Link href="/about" className="hover:text-white hover:underline">About Web3.news</Link>
|
||||
<div className="h-7 w-[1px] bg-current mx-4" />
|
||||
<Link href="/donate" className="hover:text-white hover:underline">Contribute to the cause</Link>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default Footer
|
|
@ -1,12 +0,0 @@
|
|||
type SearchResult = {
|
||||
id: number
|
||||
timestamp: Date
|
||||
title: string
|
||||
description: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
type SelectOptions = {
|
||||
value: string
|
||||
label: string
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 248, 45%, 7%;
|
||||
--popover-foreground: 0, 0%, 100%;
|
||||
|
||||
--primary: 136, 87%, 83%;
|
||||
--primary-foreground: black;
|
||||
|
||||
--secondary: 169 46% 37%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 0 0% 32%;
|
||||
--muted-foreground: 0 0% 32%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 0 0% 32%;
|
||||
--input: 0 0% 32%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 248, 45%, 7%;
|
||||
--popover-foreground: 0, 0%, 100%;
|
||||
|
||||
--primary: 136, 87%, 83%;
|
||||
--primary-foreground: black;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground font-main;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,8 @@
|
|||
import type { Config } from 'tailwindcss'
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
content: ["./app/**/*.{js,jsx,ts,tsx}"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
|
@ -62,12 +58,12 @@ const config: Config = {
|
|||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: '0' },
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: '0' },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
|
@ -77,6 +73,6 @@ const config: Config = {
|
|||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
};
|
||||
|
||||
export default config
|
||||
export default config;
|
||||
|
|
|
@ -1,41 +1,34 @@
|
|||
{
|
||||
"include": [
|
||||
"remix.env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ES2022"
|
||||
],
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2022",
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
"./app/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
"~/*": [
|
||||
"./app/*"
|
||||
]
|
||||
},
|
||||
// Remix takes care of building everything in `remix build`.
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue