api and shadcn
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
Generated
+275
-139
File diff suppressed because it is too large
Load Diff
+11
-4
@@ -11,16 +11,23 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"instagram-graph-api": "^7.0.0",
|
"instagram-graph-api": "^7.0.0",
|
||||||
|
"lucide-react": "^0.475.0",
|
||||||
|
"mongodb": "^6.13.0",
|
||||||
"next": "15.1.7",
|
"next": "15.1.7",
|
||||||
"react": "^19.0.0",
|
"next-themes": "^0.4.4",
|
||||||
"react-dom": "^19.0.0",
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0",
|
||||||
|
"tailwind-merge": "^3.0.1",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"twitter-api-v2": "^1.19.1"
|
"twitter-api-v2": "^1.19.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^18",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||||
|
import { type ThemeProviderProps } from 'next-themes';
|
||||||
|
|
||||||
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||||
|
}
|
||||||
@@ -1,13 +1,87 @@
|
|||||||
import { NextResponse, NextRequest } from 'next/server';
|
import { NextResponse, NextRequest } from 'next/server';
|
||||||
import { TwitterApi } from 'twitter-api-v2';
|
import { TwitterApi } from 'twitter-api-v2';
|
||||||
import config from '@/env';
|
import config from '@/app/api/env';
|
||||||
import { PostRequest } from '@/platform';
|
import { PostRequest } from '@/utils';
|
||||||
import { inspect } from 'util';
|
import { inspect } from 'util';
|
||||||
|
import database from '../database';
|
||||||
|
import { generateUrlSafeString } from '@/utils';
|
||||||
|
|
||||||
|
// Account creation
|
||||||
|
// Email doesn't exist
|
||||||
|
// Password is between 6-20 characters
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
|
const { email, password } = await req.json();
|
||||||
|
|
||||||
|
if (typeof email !== 'string' || typeof password !== 'string')
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
message: 'ok, feature not available yet',
|
message:
|
||||||
|
"Invalid structure, body requires 'email: string' and 'password: string'",
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (await database.users.countDocuments({ email }))
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
message: 'Email already registered',
|
||||||
|
},
|
||||||
|
{ status: 409 }
|
||||||
|
);
|
||||||
|
|
||||||
|
await database.users.insertOne({ email, password });
|
||||||
|
|
||||||
|
const response = NextResponse.json(
|
||||||
|
{
|
||||||
|
message: 'ok',
|
||||||
|
},
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
|
||||||
|
response.cookies.set('session_id', generateUrlSafeString(16));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account login
|
||||||
|
// Email exists
|
||||||
|
// Password is valid
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const { email, password } = await req.json();
|
||||||
|
|
||||||
|
if (typeof email !== 'string' || typeof password !== 'string')
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
"Invalid structure, body requires 'email: string' and 'password: string'",
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = await database.users.findOne({ email });
|
||||||
|
|
||||||
|
if (!user)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
message: 'Invalid email',
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (user.password !== password)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
message: 'Invalid password',
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const sessionId = (await database.sessions.insertOne({ email })).insertedId;
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
message: 'ok',
|
||||||
|
sessionId,
|
||||||
},
|
},
|
||||||
{ status: 200 }
|
{ status: 200 }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { MongoClient } from 'mongodb';
|
||||||
|
import config from './env';
|
||||||
|
import { generateUrlSafeString, Session, User } from '@/utils';
|
||||||
|
|
||||||
|
const client = new MongoClient(config.mongoUrl);
|
||||||
|
|
||||||
|
const db = client.db('klayrion');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
users: db.collection<User>('users'),
|
||||||
|
sessions: db.collection<Session>('sessions'),
|
||||||
|
};
|
||||||
@@ -7,6 +7,8 @@ const config = {
|
|||||||
key: process.env.TWITTER_KEY || error('TWITTER_KEY'),
|
key: process.env.TWITTER_KEY || error('TWITTER_KEY'),
|
||||||
secret: process.env.TWITTER_SECRET || error('TWITTER_SECRET'),
|
secret: process.env.TWITTER_SECRET || error('TWITTER_SECRET'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mongoUrl: process.env.MONGO_URL || error('MONGO_URL'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NextResponse, NextRequest } from 'next/server';
|
import { NextResponse, NextRequest } from 'next/server';
|
||||||
import { TwitterApi } from 'twitter-api-v2';
|
import { TwitterApi } from 'twitter-api-v2';
|
||||||
import config from '@/env';
|
import config from '@/app/api/env';
|
||||||
import { PostRequest } from '@/platform';
|
import { PostRequest } from '@/utils';
|
||||||
import { inspect } from 'util';
|
import { inspect } from 'util';
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
|
|||||||
+65
-14
@@ -2,20 +2,71 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--foreground);
|
|
||||||
background: var(--background);
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 0 0% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 0 0% 3.9%;
|
||||||
|
--primary: 0 0% 9%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
--secondary: 0 0% 96.1%;
|
||||||
|
--secondary-foreground: 0 0% 9%;
|
||||||
|
--muted: 0 0% 96.1%;
|
||||||
|
--muted-foreground: 0 0% 45.1%;
|
||||||
|
--accent: 0 0% 96.1%;
|
||||||
|
--accent-foreground: 0 0% 9%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 89.8%;
|
||||||
|
--input: 0 0% 89.8%;
|
||||||
|
--ring: 0 0% 3.9%;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--background: 0 0% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
--card: 0 0% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
--popover: 0 0% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 0 0% 9%;
|
||||||
|
--secondary: 0 0% 14.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 0 0% 14.9%;
|
||||||
|
--muted-foreground: 0 0% 63.9%;
|
||||||
|
--accent: 0 0% 14.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 14.9%;
|
||||||
|
--input: 0 0% 14.9%;
|
||||||
|
--ring: 0 0% 83.1%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+20
-19
@@ -1,22 +1,11 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from 'next';
|
||||||
import { Roboto, Roboto_Mono } from "next/font/google";
|
import { cn } from '@/lib/utils';
|
||||||
import "./globals.css";
|
import './globals.css';
|
||||||
|
import { ThemeProvider } from './ThemeProvider';
|
||||||
const roboto = Roboto({
|
|
||||||
weight: "500",
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-roboto",
|
|
||||||
});
|
|
||||||
|
|
||||||
const robotoMono = Roboto_Mono({
|
|
||||||
weight: "500",
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-roboto-mono",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Klayrion: The best AI social media scheduling tool",
|
title: 'Create Next App',
|
||||||
description: "Generated by create next app",
|
description: 'Generated by create next app',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -25,9 +14,21 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={`${roboto.variable} ${robotoMono.variable} antialiased`}>
|
<body
|
||||||
|
suppressHydrationWarning
|
||||||
|
className={cn(
|
||||||
|
'min-h-screen bg-background font-sans antialiased'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="dark"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
return <div className="bg-[#ccc] w-1/2 h-screen ml-auto"></div>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
export default function SignUp() {
|
||||||
|
return <div className="bg-[#ccc] w-1/2 h-screen ml-auto"></div>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
export interface Platform {
|
|
||||||
platform: 'x';
|
|
||||||
token: string;
|
|
||||||
secret: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PostRequest {
|
|
||||||
content: string;
|
|
||||||
platforms: Platform[];
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
export function generateUrlSafeString(length = 16) {
|
||||||
|
const chars =
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||||
|
const array = new Uint8Array(length);
|
||||||
|
crypto.getRandomValues(array);
|
||||||
|
|
||||||
|
return Array.from(array, (byte) => chars[byte % chars.length]).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Platform {
|
||||||
|
platform: 'x';
|
||||||
|
token: string;
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostRequest {
|
||||||
|
content: string;
|
||||||
|
platforms: Platform[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Session = { email: string };
|
||||||
+47
-3
@@ -1,6 +1,7 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
darkMode: ["class"],
|
||||||
content: [
|
content: [
|
||||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
@@ -9,10 +10,53 @@ export default {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
background: "var(--background)",
|
background: 'hsl(var(--background))',
|
||||||
foreground: "var(--foreground)",
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
},
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
},
|
},
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
},
|
},
|
||||||
plugins: [],
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
|
},
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
chart: {
|
||||||
|
'1': 'hsl(var(--chart-1))',
|
||||||
|
'2': 'hsl(var(--chart-2))',
|
||||||
|
'3': 'hsl(var(--chart-3))',
|
||||||
|
'4': 'hsl(var(--chart-4))',
|
||||||
|
'5': 'hsl(var(--chart-5))'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [require("tailwindcss-animate")],
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
|||||||
Reference in New Issue
Block a user