api and shadcn

This commit is contained in:
2025-02-14 12:29:20 +01:00
parent 63c47fb09e
commit f19a38ee0c
16 changed files with 591 additions and 202 deletions
+21
View File
@@ -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"
}
+275 -139
View File
File diff suppressed because it is too large Load Diff
+11 -4
View File
@@ -11,16 +11,23 @@
},
"dependencies": {
"axios": "^1.7.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"instagram-graph-api": "^7.0.0",
"lucide-react": "^0.475.0",
"mongodb": "^6.13.0",
"next": "15.1.7",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next-themes": "^0.4.4",
"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"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react": "^18",
"@types/react-dom": "^18",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
+9
View File
@@ -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>;
}
+77 -3
View File
@@ -1,13 +1,87 @@
import { NextResponse, NextRequest } from 'next/server';
import { TwitterApi } from 'twitter-api-v2';
import config from '@/env';
import { PostRequest } from '@/platform';
import config from '@/app/api/env';
import { PostRequest } from '@/utils';
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) {
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 }
);
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, feature not available yet',
message: 'ok',
sessionId,
},
{ status: 200 }
);
+12
View File
@@ -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'),
};
+2
View File
@@ -7,6 +7,8 @@ const config = {
key: process.env.TWITTER_KEY || error('TWITTER_KEY'),
secret: process.env.TWITTER_SECRET || error('TWITTER_SECRET'),
},
mongoUrl: process.env.MONGO_URL || error('MONGO_URL'),
};
export default config;
+2 -2
View File
@@ -1,7 +1,7 @@
import { NextResponse, NextRequest } from 'next/server';
import { TwitterApi } from 'twitter-api-v2';
import config from '@/env';
import { PostRequest } from '@/platform';
import config from '@/app/api/env';
import { PostRequest } from '@/utils';
import { inspect } from 'util';
export async function POST(req: NextRequest) {
+61 -10
View File
@@ -2,20 +2,71 @@
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
body {
font-family: Arial, Helvetica, sans-serif;
}
@media (prefers-color-scheme: dark) {
@layer base {
:root {
--background: #0a0a0a;
--foreground: #ededed;
--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%;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
+27 -26
View File
@@ -1,34 +1,35 @@
import type { Metadata } from "next";
import { Roboto, Roboto_Mono } from "next/font/google";
import "./globals.css";
const roboto = Roboto({
weight: "500",
subsets: ["latin"],
variable: "--font-roboto",
});
const robotoMono = Roboto_Mono({
weight: "500",
subsets: ["latin"],
variable: "--font-roboto-mono",
});
import type { Metadata } from 'next';
import { cn } from '@/lib/utils';
import './globals.css';
import { ThemeProvider } from './ThemeProvider';
export const metadata: Metadata = {
title: "Klayrion: The best AI social media scheduling tool",
description: "Generated by create next app",
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
children,
}: Readonly<{
children: React.ReactNode;
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${roboto.variable} ${robotoMono.variable} antialiased`}>
{children}
</body>
</html>
);
return (
<html lang="en" suppressHydrationWarning>
<body
suppressHydrationWarning
className={cn(
'min-h-screen bg-background font-sans antialiased'
)}
>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}
+5
View File
@@ -0,0 +1,5 @@
'use client';
export default function Login() {
return <div className="bg-[#ccc] w-1/2 h-screen ml-auto"></div>;
}
+5
View File
@@ -0,0 +1,5 @@
'use client';
export default function SignUp() {
return <div className="bg-[#ccc] w-1/2 h-screen ml-auto"></div>;
}
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
-10
View File
@@ -1,10 +0,0 @@
export interface Platform {
platform: 'x';
token: string;
secret: string;
}
export interface PostRequest {
content: string;
platforms: Platform[];
}
+26
View File
@@ -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 };
+52 -8
View File
@@ -1,18 +1,62 @@
import type { Config } from "tailwindcss";
export default {
content: [
darkMode: ["class"],
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
},
},
extend: {
colors: {
background: 'hsl(var(--background))',
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))'
},
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: [],
plugins: [require("tailwindcss-animate")],
} satisfies Config;