feat: update major change template
This commit is contained in:
parent
4ea8888da9
commit
6b0c866e05
|
|
@ -0,0 +1,23 @@
|
|||
import LayoutProvider from "@/providers/layout.provider";
|
||||
import LayoutContentProvider from "@/providers/content.provider";
|
||||
import DashCodeSidebar from "@/components/partials/sidebar";
|
||||
import DashCodeFooter from "@/components/partials/footer";
|
||||
import ThemeCustomize from "@/components/partials/customizer";
|
||||
import DashCodeHeader from "@/components/partials/header";
|
||||
import MountedProvider from "@/providers/mounted.provider";
|
||||
|
||||
const layout = async ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<MountedProvider isProtected={true}>
|
||||
<LayoutProvider>
|
||||
<ThemeCustomize />
|
||||
<DashCodeHeader />
|
||||
<DashCodeSidebar />
|
||||
<LayoutContentProvider>{children}</LayoutContentProvider>
|
||||
<DashCodeFooter />
|
||||
</LayoutProvider>
|
||||
</MountedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default layout;
|
||||
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 228 KiB |
|
|
@ -0,0 +1,295 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 215.3 19.3% 34.5%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--success: 154 52% 55%;
|
||||
--success-foreground: 138.5 76.5% 96.7%;
|
||||
|
||||
--warning: 16 93% 70%;
|
||||
--warning-foreground: 33.3 100% 96.5%;
|
||||
|
||||
--info: 185 96% 51%;
|
||||
--info-foreground: 183.2 100% 96.3%;
|
||||
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
--default-50: 210 40% 98%;
|
||||
--default-100: 210 40% 96.1%;
|
||||
--default-200: 214.3 31.8% 91.4%;
|
||||
--default-300: 212.7 26.8% 83.9%;
|
||||
--default-400: 215 20.2% 65.1%;
|
||||
--default-500: 215.4 16.3% 46.9%;
|
||||
--default-600: 215.3 19.3% 34.5%;
|
||||
--default-700: 215.3 25% 26.7%;
|
||||
--default-800: 217.2 32.6% 17.5%;
|
||||
--default-900: 222.2 47.4% 11.2%;
|
||||
--default-950: 222.2 84% 4.9%;
|
||||
--default: 222.2 47.4% 11.2%;
|
||||
--default-foreground: 210 40% 98%;
|
||||
|
||||
--primary-50: 213.8 100% 96.9%;
|
||||
--primary-100: 214.3 94.6% 92.7%;
|
||||
--primary-200: 213.3 96.9% 87.3%;
|
||||
--primary-300: 211.7 96.4% 78.4%;
|
||||
--primary-400: 213.1 93.9% 67.8%;
|
||||
--primary-500: 217.2 91.2% 59.8%;
|
||||
--primary-600: 221.2 83.2% 53.3%;
|
||||
--primary-700: 224.3 76.3% 48%;
|
||||
--primary-800: 225.9 70.7% 40.2%;
|
||||
--primary-900: 224.4 64.3% 32.9%;
|
||||
--primary-950: 226.2 57% 21%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--sidebar: 0 0% 100%;
|
||||
--header: 0 0% 100%;
|
||||
--menu-arrow: 228, 45%, 98%;
|
||||
--menu-arrow-active: 212.7 26.8% 83.9%;
|
||||
--menu-foreground: 215, 20%, 65%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 47.4% 11.2%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--card: 215 27.9% 16.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 215.3 25% 26.7%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--success: 154 52% 55%;
|
||||
--success-foreground: 138.5 76.5% 96.7%;
|
||||
|
||||
--warning: 16 93% 70%;
|
||||
--warning-foreground: 33.3 100% 96.5%;
|
||||
|
||||
--info: 185 96% 51%;
|
||||
--info-foreground: 183.2 100% 96.3%;
|
||||
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
|
||||
--default-950: 210 40% 98%;
|
||||
--default-900: 210 40% 96.1%;
|
||||
--default-800: 214.3 31.8% 91.4%;
|
||||
--default-700: 212.7 26.8% 83.9%;
|
||||
--default-600: 215 20.2% 65.1%;
|
||||
--default-500: 215.4 16.3% 46.9%;
|
||||
--default-400: 215.3 19.3% 34.5%;
|
||||
--default-300: 215.3 25% 26.7%;
|
||||
--default-200: 217.2 32.6% 17.5%;
|
||||
--default-100: 222.2 47.4% 11.2%;
|
||||
--default-50: 222.2 84% 4.9%;
|
||||
--default: 213.8 100% 96.9%;
|
||||
--default-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--sidebar: 215 27.9% 16.9%;
|
||||
--header: 215 27.9% 16.9%;
|
||||
--menu-arrow: 215.3 25% 26.7%;
|
||||
--menu-arrow-active: 215.3 19.3% 34.5%;
|
||||
--menu-foreground: 214.3 31.8% 91.4%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply text-foreground;
|
||||
}
|
||||
|
||||
.input-group :not(:first-child) input {
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
.input-group.merged :not(:first-child) input {
|
||||
border-left-width: 0 !important;
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
|
||||
.input-group :not(:last-child) input {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.input-group.merged :not(:last-child) input {
|
||||
border-right: 0px !important;
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeProvider } from "@/providers/theme-provider";
|
||||
import MountedProvider from "@/providers/mounted.provider";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { Toaster as SonnerToaster } from "@/components/ui/sonner";
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: 'swap',
|
||||
preload: true,
|
||||
fallback: ['system-ui', 'arial']
|
||||
});
|
||||
// language
|
||||
import { getLangDir } from "rtl-detect";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages } from "next-intl/server";
|
||||
import DirectionProvider from "@/providers/direction-provider";
|
||||
import AuthProvider from "@/providers/auth.provider";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "NetidHub",
|
||||
description: "NetidHub Platform",
|
||||
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'),
|
||||
openGraph: {
|
||||
title: "NetidHub",
|
||||
description: "NetidHub Platform",
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: "NetidHub",
|
||||
description: "NetidHub Platform",
|
||||
},
|
||||
other: {
|
||||
'X-DNS-Prefetch-Control': 'on',
|
||||
},
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params: { locale },
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
params: { locale: string };
|
||||
}>) {
|
||||
const messages = await getMessages();
|
||||
const direction = getLangDir(locale);
|
||||
return (
|
||||
<html lang={locale} dir={direction}>
|
||||
<head>
|
||||
{/* DNS Prefetch for external domains */}
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com" />
|
||||
|
||||
{/* Preconnect to external domains */}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://fonts.gstatic.com"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
|
||||
{/* Preload critical fonts */}
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
|
||||
as="style"
|
||||
/>
|
||||
<noscript>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</noscript>
|
||||
</head>
|
||||
<body className={`${inter.className} dashcode-app`}>
|
||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||
<AuthProvider>
|
||||
<ThemeProvider attribute="class" defaultTheme="light">
|
||||
<DirectionProvider direction={direction}>
|
||||
{children}
|
||||
</DirectionProvider>
|
||||
<Toaster />
|
||||
<SonnerToaster />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
122
app/globals.css
122
app/globals.css
|
|
@ -1,122 +0,0 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
// const geistSans = Geist({
|
||||
// variable: "--font-geist-sans",
|
||||
// subsets: ["latin"],
|
||||
// });
|
||||
|
||||
// const geistMono = Geist_Mono({
|
||||
// variable: "--font-geist-mono",
|
||||
// subsets: ["latin"],
|
||||
// });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "NetidHub",
|
||||
description: "NetidHub",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,21 +1,17 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
"use client"
|
||||
import dynamic from "next/dynamic";
|
||||
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useTheme } from "next-themes";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
interface EarningBlockProps {
|
||||
title?: string,
|
||||
className?: string;
|
||||
colors?: string[];
|
||||
series?: number[];
|
||||
labels?: string[];
|
||||
height?: number;
|
||||
chartType?: 'donut' | 'pie' | 'radialBar';
|
||||
total?: number | string;
|
||||
percentage?: string;
|
||||
}
|
||||
|
||||
const EarningBlock = ({
|
||||
title = "Earnings",
|
||||
total = "$0",
|
||||
percentage = "+08%",
|
||||
series = [70, 30],
|
||||
chartType = "donut",
|
||||
height = 200,
|
||||
labels = ["Success", "Return"],
|
||||
colors = ["#ffbf99", "#5cffff"],
|
||||
className = "",
|
||||
}: EarningBlockProps) => {
|
||||
const { theme: mode } = useTheme();
|
||||
const options: any = {
|
||||
labels: labels,
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
tooltip: {
|
||||
theme: mode === "dark" ? "dark" : "light",
|
||||
},
|
||||
colors: [...colors],
|
||||
legend: {
|
||||
position: "bottom",
|
||||
fontSize: "12px",
|
||||
fontFamily: "Outfit",
|
||||
fontWeight: 400,
|
||||
labels: {
|
||||
colors: mode === "dark" ? "#cbd5e1" : "#0f172a",
|
||||
}
|
||||
},
|
||||
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
size: "70%",
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
show: false,
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
fontFamily: "Inter",
|
||||
},
|
||||
value: {
|
||||
show: true,
|
||||
fontSize: "16px",
|
||||
fontFamily: "Outfit",
|
||||
color: mode === "dark" ? "#cbd5e1" : "#0f172a",
|
||||
formatter(val: string) {
|
||||
return `${parseInt(val)}%`;
|
||||
},
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
fontSize: "10px",
|
||||
label: "",
|
||||
formatter() {
|
||||
return "70";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const t = useTranslations("EcommerceDashboard");
|
||||
return (
|
||||
<Card className={cn("", className)}>
|
||||
<CardContent className="py-3 px-4">
|
||||
<div className="flex flex-col gap-2 md:flex-row items-center">
|
||||
<div className="flex-1">
|
||||
<div className="text-sm text-default-600 mb-1.5">{title}</div>
|
||||
<div className="text-lg text-default-900 font-medium mb-1.5">
|
||||
{total}
|
||||
</div>
|
||||
<div className="font-normal text-xs text-default-600 whitespace-nowrap">
|
||||
<span className="text-primary me-1">{percentage}</span>
|
||||
{t("statistics_graph_desc", { defaultValue: "Statistics Graph Desc" })}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Chart
|
||||
options={options}
|
||||
series={series}
|
||||
type={chartType}
|
||||
height={height}
|
||||
width={"100%"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default EarningBlock;
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
"use client";
|
||||
import dynamic from "next/dynamic";
|
||||
const Chart = dynamic(() => import("react-apexcharts"));
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { useTheme } from "next-themes";
|
||||
interface OrdersnBlockProps {
|
||||
className?: string;
|
||||
series?: number[];
|
||||
chartColor?: string;
|
||||
chartType?: 'area' | 'bar' | 'line' | 'pie' | 'donut' | 'radialBar'
|
||||
opacity?: number,
|
||||
title?: string,
|
||||
total?: number | string,
|
||||
height?: number,
|
||||
percentageContent?: React.ReactNode
|
||||
}
|
||||
|
||||
const OrdersBlock = ({
|
||||
series = [15, 30, 15, 30, 20, 35],
|
||||
chartColor = "#0f172a",
|
||||
chartType = "bar",
|
||||
opacity = 1,
|
||||
className,
|
||||
title = "Order Block",
|
||||
total,
|
||||
height = 42,
|
||||
percentageContent = <span className="text-warning">-60% </span>
|
||||
}: OrdersnBlockProps) => {
|
||||
const { theme: mode } = useTheme();
|
||||
const chartSeries = [
|
||||
{
|
||||
data: series
|
||||
}
|
||||
];
|
||||
|
||||
const options: any = {
|
||||
chart: {
|
||||
toolbar: {
|
||||
autoSelected: "pan",
|
||||
show: false,
|
||||
},
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
zoom: {
|
||||
enabled: false,
|
||||
},
|
||||
sparkline: {
|
||||
enabled: true,
|
||||
}
|
||||
},
|
||||
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: "60%",
|
||||
barHeight: "100%",
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
stroke: {
|
||||
width: 2,
|
||||
curve: "straight",
|
||||
},
|
||||
markers: {
|
||||
size: 3,
|
||||
colors: chartColor,
|
||||
strokeColors: chartColor,
|
||||
strokeWidth: 2,
|
||||
shape: "circle",
|
||||
radius: 2,
|
||||
hover: {
|
||||
sizeOffset: 1,
|
||||
},
|
||||
},
|
||||
colors: [chartColor],
|
||||
tooltip: {
|
||||
theme: mode === "dark" ? "dark" : "light",
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
show: false,
|
||||
},
|
||||
fill: {
|
||||
type: "solid",
|
||||
opacity: [opacity],
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
xaxis: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
show: false,
|
||||
labels: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
show: false,
|
||||
},
|
||||
axisBorder: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
show: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Card className={cn("p-4", className)}>
|
||||
<CardContent className="p-0 ">
|
||||
{
|
||||
title && (
|
||||
<div className="text-sm text-default-600 mb-1.5">
|
||||
{title}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{total && (
|
||||
<div className="text-lg text-default-900 font-medium mb-1.5">
|
||||
{total}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="font-normal text-xs text-default-600">
|
||||
{percentageContent}
|
||||
<span className="ms-1">From last Week</span>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Chart
|
||||
options={options}
|
||||
series={chartSeries}
|
||||
type={chartType}
|
||||
height={height}
|
||||
width={"100%"}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card >
|
||||
);
|
||||
};
|
||||
|
||||
export default OrdersBlock;
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
"use client";
|
||||
import dynamic from "next/dynamic";
|
||||
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Card, CardContent } from "../ui/card";
|
||||
|
||||
interface ProgressBlockProps {
|
||||
title?: string,
|
||||
className?: string;
|
||||
colors?: string[];
|
||||
series?: number[];
|
||||
labels?: string[];
|
||||
height?: number;
|
||||
chartType?: 'donut' | 'pie' | 'radialBar';
|
||||
}
|
||||
|
||||
const ProgressBlock = ({
|
||||
className,
|
||||
height = 200,
|
||||
title,
|
||||
labels = ["Complete", "Left"],
|
||||
series = [70, 30],
|
||||
chartType = "donut",
|
||||
colors = ["#0CE7FA", "#E2F6FD"] }: ProgressBlockProps) => {
|
||||
const { theme: mode } = useTheme();
|
||||
|
||||
const options: any = {
|
||||
labels: labels,
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
colors: [...colors],
|
||||
legend: {
|
||||
position: "bottom",
|
||||
fontSize: "12px",
|
||||
fontFamily: "Outfit",
|
||||
fontWeight: 400,
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
theme: mode === "dark" ? "dark" : "light",
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
size: "40%",
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
show: false,
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
fontFamily: "Inter",
|
||||
},
|
||||
value: {
|
||||
show: true,
|
||||
fontSize: "16px",
|
||||
fontFamily: "Outfit",
|
||||
color: mode === "dark" ? "#cbd5e1" : "#0f172a",
|
||||
formatter(val: string) {
|
||||
return `${parseInt(val)}%`;
|
||||
},
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
fontSize: "10px",
|
||||
label: "",
|
||||
formatter() {
|
||||
return "70";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={cn("", className)}>
|
||||
<CardContent className="py-[18px] px-4">
|
||||
{
|
||||
title &&
|
||||
<div className="text-default-500 dark:text-default-900 text-sm font-medium mb-3">
|
||||
{title}
|
||||
</div>
|
||||
}
|
||||
<Chart
|
||||
options={options}
|
||||
series={series}
|
||||
type={chartType}
|
||||
height={height}
|
||||
width={"100%"}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressBlock;
|
||||
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
"use client"
|
||||
import dynamic from "next/dynamic";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { useTheme } from "next-themes";
|
||||
const Chart = dynamic(() => import("react-apexcharts"));
|
||||
interface StatsBlock {
|
||||
className?: string;
|
||||
title: string;
|
||||
total?: number | string;
|
||||
series?: number[];
|
||||
chartColor?: string;
|
||||
chartType?: 'area' | 'bar' | 'line' | 'pie' | 'donut' | 'radialBar';
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
const defaultData = [800, 600, 1000, 800, 600, 1000, 800, 900];
|
||||
|
||||
|
||||
const StatisticsBlock = ({
|
||||
title = " Static Block",
|
||||
total,
|
||||
className,
|
||||
series = defaultData,
|
||||
chartColor = "#00EBFF",
|
||||
chartType = "area",
|
||||
opacity = 0.1
|
||||
|
||||
}: StatsBlock) => {
|
||||
const { theme: mode } = useTheme();
|
||||
const chartSeries = [
|
||||
{
|
||||
data: series
|
||||
}
|
||||
];
|
||||
|
||||
const options: any = {
|
||||
chart: {
|
||||
toolbar: {
|
||||
autoSelected: "pan",
|
||||
show: false,
|
||||
},
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
zoom: {
|
||||
enabled: false,
|
||||
},
|
||||
sparkline: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
stroke: {
|
||||
curve: "smooth",
|
||||
width: 2,
|
||||
},
|
||||
colors: [chartColor],
|
||||
|
||||
tooltip: {
|
||||
theme: mode === "dark" ? "dark" : "light",
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
show: false,
|
||||
},
|
||||
fill: {
|
||||
type: "solid",
|
||||
opacity: [opacity],
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
xaxis: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
show: false,
|
||||
labels: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
show: false,
|
||||
},
|
||||
axisBorder: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
show: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Card className={cn(" ", className)}>
|
||||
<CardContent className=" py-[18px] px-4 ">
|
||||
|
||||
<div className="flex gap-6">
|
||||
<div className="flex-none">
|
||||
<Chart
|
||||
options={options}
|
||||
series={chartSeries}
|
||||
type={chartType}
|
||||
height={48}
|
||||
width={48}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-default-800 text-sm mb-1 font-medium">
|
||||
{title}
|
||||
</div>
|
||||
<div className="text-default-900 text-lg font-medium">
|
||||
{total}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export { StatisticsBlock };
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
"use client"
|
||||
import dynamic from "next/dynamic";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
const Chart = dynamic(() => import("react-apexcharts"));
|
||||
import { useTheme } from "next-themes";
|
||||
interface StatusBlockProps {
|
||||
className?: string;
|
||||
icon?: React.ReactNode;
|
||||
title?: string;
|
||||
total?: number | string;
|
||||
series?: number[];
|
||||
chartColor?: string;
|
||||
iconWrapperClass?: string;
|
||||
chartType?: 'area' | 'bar' | 'line' | 'pie' | 'donut' | 'radialBar'
|
||||
reverse?: boolean
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
const StatusBlock = ({
|
||||
title,
|
||||
total,
|
||||
className,
|
||||
icon,
|
||||
series = [800, 600, 1000, 800, 600, 1000, 800, 900],
|
||||
chartColor = "#0ce7fa",
|
||||
iconWrapperClass,
|
||||
chartType = "area",
|
||||
reverse = false,
|
||||
opacity = 0.1
|
||||
}: StatusBlockProps) => {
|
||||
const { theme: mode } = useTheme();
|
||||
const chartSeries = [
|
||||
{
|
||||
data: series
|
||||
}
|
||||
];
|
||||
|
||||
const options: any = {
|
||||
chart: {
|
||||
toolbar: {
|
||||
autoSelected: "pan",
|
||||
show: false,
|
||||
},
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
zoom: {
|
||||
enabled: false,
|
||||
},
|
||||
sparkline: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: "60%",
|
||||
barHeight: "100%",
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
stroke: {
|
||||
curve: "smooth",
|
||||
width: 2,
|
||||
},
|
||||
colors: [chartColor],
|
||||
|
||||
tooltip: {
|
||||
theme: mode === "dark" ? "dark" : "light",
|
||||
},
|
||||
|
||||
grid: {
|
||||
show: false,
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
show: false,
|
||||
},
|
||||
fill: {
|
||||
type: "solid",
|
||||
opacity: [opacity],
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
xaxis: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
show: false,
|
||||
labels: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
show: false,
|
||||
},
|
||||
axisBorder: {
|
||||
low: 0,
|
||||
offsetX: 0,
|
||||
show: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Card className={cn('', className,)}>
|
||||
<CardContent className="p-4">
|
||||
<div className={cn('flex gap-3', {
|
||||
'flex-row-reverse': reverse
|
||||
})}>
|
||||
{
|
||||
icon &&
|
||||
<div className="flex-none">
|
||||
<div
|
||||
className={cn("h-12 w-12 rounded-full flex flex-col items-center justify-center text-2xl bg-default/10", iconWrapperClass)}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{(title || total) && (
|
||||
<div className="flex-1">
|
||||
{title && (
|
||||
<div className="text-default-600 text-sm font-medium">
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
{total && (
|
||||
<div className="text-default-900 text-lg font-medium">
|
||||
{total}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className="ms-auto max-w-[124px]">
|
||||
<Chart
|
||||
options={options}
|
||||
series={chartSeries}
|
||||
type={chartType}
|
||||
height={41}
|
||||
width={124}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card >
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export { StatusBlock };
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface UpgradeProps {
|
||||
image?: any;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
const UpgradeBlock = ({ children, className }: UpgradeProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn("p-6 relative bg-default-900 rounded-2xl", className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export { UpgradeBlock };
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
import { cn } from "@/lib/utils"
|
||||
interface WelcomeProps {
|
||||
image?: any;
|
||||
children: React.ReactNode;
|
||||
badge?: string;
|
||||
className?: string;
|
||||
}
|
||||
const WelcomeBlock = ({ children, className, }: WelcomeProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn('bg-no-repeat bg-cover bg-center p-4 rounded-md relative z-10', className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BlockBadge = ({ children, className }: { children: React.ReactNode, className?: string }) => {
|
||||
return (
|
||||
<div className={cn('absolute z-10 top-1/2 -translate-y-1/2 end-6 mt-2 h-12 w-12 bg-primary-foreground dark:bg-default-900 dark:text-default-100 rounded-full text-xs font-medium flex flex-col items-center justify-center', className)} >
|
||||
{children}
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
export { WelcomeBlock, BlockBadge };
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
"use client";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { CopyButton } from "./copy-button";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
const CardSnippet = ({ title, code, children }: { title: string, code: string, children: React.ReactNode }) => {
|
||||
const [show, setShow] = useState(false);
|
||||
const toggle = () => {
|
||||
setShow(!show);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
{title && (
|
||||
<CardTitle className="flex-1 leading-normal"> {title}</CardTitle>
|
||||
)}
|
||||
{code && (
|
||||
<div className="flex-none ">
|
||||
<Switch id="airplane-mode" onClick={toggle} />
|
||||
|
||||
</div>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{children}
|
||||
<Collapsible open={show}>
|
||||
<CollapsibleContent className="CollapsibleContent relative" >
|
||||
<div className="absolute end-2 top-2">
|
||||
<CopyButton
|
||||
event="copy_chart_code"
|
||||
name={title}
|
||||
code={code}
|
||||
className="[&_svg]-h-3 h-6 w-6 rounded-[6px] bg-background hover:bg-background hover:text-foreground text-foreground shadow-none [&_svg]:w-3"
|
||||
|
||||
/></div>
|
||||
<SyntaxHighlighter
|
||||
language="javascript"
|
||||
className=" rounded-md text-sm mt-6 "
|
||||
style={atomOneDark}
|
||||
customStyle={{
|
||||
padding: "24px",
|
||||
}}
|
||||
>
|
||||
{`${code}`}
|
||||
</SyntaxHighlighter>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardSnippet;
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CheckIcon, ClipboardIcon } from "lucide-react"
|
||||
|
||||
import { Event, trackEvent } from "@/lib/events"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button, ButtonProps } from "@/components/ui/button"
|
||||
import {
|
||||
TooltipProvider,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
|
||||
export function CopyButton({
|
||||
event,
|
||||
name,
|
||||
code,
|
||||
className,
|
||||
tooltip = "Copy code",
|
||||
...props
|
||||
}: {
|
||||
event: Event["name"]
|
||||
name: string
|
||||
code: string
|
||||
tooltip?: string
|
||||
} & ButtonProps) {
|
||||
const [hasCopied, setHasCopied] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setHasCopied(false)
|
||||
}, 2000)
|
||||
}, [hasCopied])
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className={cn(
|
||||
"[&_svg]-h-3.5 h-7 w-7 rounded-[6px] [&_svg]:w-3.5 border-default-500 text-default-500 ",
|
||||
className
|
||||
)}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(code)
|
||||
trackEvent({
|
||||
name: event,
|
||||
properties: {
|
||||
name,
|
||||
},
|
||||
})
|
||||
setHasCopied(true)
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<span className="sr-only">Copy</span>
|
||||
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-default text-default-foreground text-sm font-normal">{tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react'
|
||||
type IconProps = React.HTMLAttributes<SVGElement>
|
||||
const DashCodeLogo = (props: IconProps) => {
|
||||
return (
|
||||
<>
|
||||
<svg
|
||||
{...props}
|
||||
width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 6C0 2.68629 2.68629 0 6 0H26C29.3137 0 32 2.68629 32 6V32H6C2.68629 32 0 29.3137 0 26V6Z" fill="currentColor" />
|
||||
<path d="M20 23.2C19.0133 23.2 18.0933 23.02 17.24 22.66C16.4 22.3 15.6667 21.7933 15.04 21.14C14.4133 20.4867 13.9267 19.7333 13.58 18.88C13.2467 18.0133 13.08 17.0867 13.08 16.1C13.08 15.1133 13.2467 14.1933 13.58 13.34C13.9267 12.4733 14.4067 11.72 15.02 11.08C15.6467 10.44 16.38 9.94 17.22 9.58C18.06 9.22 18.9667 9.04 19.94 9.04C20.9933 9.04 21.9333 9.22667 22.76 9.6C23.6 9.97333 24.3333 10.4867 24.96 11.14L23.96 12.14C23.48 11.6067 22.9 11.1933 22.22 10.9C21.54 10.5933 20.78 10.44 19.94 10.44C19.1667 10.44 18.4533 10.58 17.8 10.86C17.16 11.14 16.5933 11.54 16.1 12.06C15.62 12.5667 15.2467 13.1667 14.98 13.86C14.7267 14.54 14.6 15.2867 14.6 16.1C14.6 16.9133 14.7333 17.6667 15 18.36C15.2667 19.0533 15.64 19.66 16.12 20.18C16.6 20.6867 17.1667 21.08 17.82 21.36C18.4733 21.64 19.1867 21.78 19.96 21.78C20.84 21.78 21.62 21.6267 22.3 21.32C22.9933 21.0133 23.58 20.5933 24.06 20.06L25.06 21.08C24.4467 21.7333 23.7133 22.2533 22.86 22.64C22.0067 23.0133 21.0533 23.2 20 23.2Z" fill='currentColor' />
|
||||
<path d="M9.3 23V20.58H12.88C13.7867 20.58 14.58 20.3933 15.26 20.02C15.94 19.6333 16.4667 19.0933 16.84 18.4C17.2267 17.7067 17.42 16.8867 17.42 15.94C17.42 15.02 17.2267 14.22 16.84 13.54C16.4533 12.8467 15.92 12.3133 15.24 11.94C14.56 11.5533 13.7733 11.36 12.88 11.36H9.24V8.94H12.92C13.96 8.94 14.92 9.11333 15.8 9.46C16.6933 9.80667 17.4667 10.3 18.12 10.94C18.7867 11.5667 19.3 12.3067 19.66 13.16C20.0333 14.0133 20.22 14.9467 20.22 15.96C20.22 16.9733 20.0333 17.9133 19.66 18.78C19.3 19.6333 18.7933 20.38 18.14 21.02C17.4867 21.6467 16.7133 22.1333 15.82 22.48C14.94 22.8267 13.9867 23 12.96 23H9.3ZM7.44 23V8.94H10.16V23H7.44Z" fill='currentColor' />
|
||||
</svg>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashCodeLogo
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
const DashboardDropdown = () => {
|
||||
const t = useTranslations("AnalyticsDashboard");
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="w-6 h-6 bg-transparent border border-default-300 hover:bg-transparent ring-offset-transparent hover:ring-0 hover:ring-transparent "
|
||||
>
|
||||
<MoreHorizontal className="w-4 h-4 text-default-600" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[140px] p-0">
|
||||
<DropdownMenuItem className="py-2 rounded-none border-b border-default-200 text-default-900 focus:bg-default-400 focus:text-default-100 dark:focus:text-default-900">
|
||||
{t("last_28_days", { defaultValue: "Last 28 Days" })}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="py-2 rounded-none border-b border-default-200 text-default-900 focus:bg-default-400 focus:text-default-100 dark:focus:text-default-900">
|
||||
{t("last_months", { defaultValue: "Last Months" })}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="py-2 rounded-none text-default-900 focus:bg-default-400 focus:text-default-100 dark:focus:text-default-900">
|
||||
{t("last_year", { defaultValue: "Last Year" })}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardDropdown;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { format } from "date-fns";
|
||||
import { Calendar as CalendarIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
export default function DateRangePicker({ className }: { className?: string }) {
|
||||
const [date, setDate] = React.useState<any | null>(null);
|
||||
const { theme: mode } = useTheme();
|
||||
|
||||
return (
|
||||
<div className={cn("grid gap-2", className)}>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
|
||||
className={cn(" font-normal", {
|
||||
" bg-background hover:bg-background hover:ring-background text-default-600": mode !== "dark",
|
||||
})}
|
||||
>
|
||||
<CalendarIcon className="ltr:mr-2 rtl:ml-2 h-4 w-4" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="end">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import React, { useState, useTransition } from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const DeleteConfirmationDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
onConfirm,
|
||||
defaultToast = true,
|
||||
toastMessage = "Successfully deleted",
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm?: () => Promise<void>;
|
||||
defaultToast?: boolean;
|
||||
toastMessage?: string;
|
||||
}) => {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!onConfirm) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
onConfirm();
|
||||
|
||||
onClose();
|
||||
if (defaultToast) {
|
||||
toast.success(toastMessage, {
|
||||
position: "top-right",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog open={open}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account and remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={onClose}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className={isPending ? "pointer-events-none" : ""}
|
||||
onClick={() => startTransition(handleConfirm)}
|
||||
>
|
||||
{isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{isPending ? "Deleting.." : "Continue"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteConfirmationDialog;
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { CKEditor } from '@ckeditor/ckeditor5-react';
|
||||
import { ClassicEditor } from 'ckeditor5';
|
||||
|
||||
// Dynamically import CKEditor to avoid SSR issues
|
||||
const DynamicCKEditor = dynamic(() => Promise.resolve(CKEditor), {
|
||||
ssr: false,
|
||||
loading: () => <div>Loading editor...</div>
|
||||
});
|
||||
|
||||
interface EditorProps {
|
||||
data?: string;
|
||||
onChange?: (data: string) => void;
|
||||
config?: any;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function Editor({ data = '', onChange, config = {}, disabled = false }: EditorProps) {
|
||||
return (
|
||||
<div className="ckeditor-wrapper">
|
||||
<DynamicCKEditor
|
||||
editor={ClassicEditor}
|
||||
data={data}
|
||||
onChange={(event, editor) => {
|
||||
const data = editor.getData();
|
||||
onChange?.(data);
|
||||
}}
|
||||
config={{
|
||||
toolbar: [
|
||||
'heading', '|',
|
||||
'bold', 'italic', 'link', '|',
|
||||
'bulletedList', 'numberedList', '|',
|
||||
'outdent', 'indent', '|',
|
||||
'blockQuote', 'insertTable', '|',
|
||||
'undo', 'redo'
|
||||
],
|
||||
...config
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
'use client'
|
||||
import React from "react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import DashCodeLogo from "./dascode-logo";
|
||||
import { useMounted } from "@/hooks/use-mounted";
|
||||
import Image from "next/image";
|
||||
const Loader = () => {
|
||||
const mounted = useMounted()
|
||||
return (
|
||||
mounted ? null : <div className=" h-screen flex items-center justify-center flex-col space-y-2">
|
||||
<div className="flex gap-2 items-center ">
|
||||
{/* <DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" /> */}
|
||||
<Image
|
||||
src="/assets/mediahub-logo-min.png"
|
||||
alt=""
|
||||
width={80}
|
||||
height={80}
|
||||
className="mb-4 w-full h-full"
|
||||
/>
|
||||
{/* <h1 className="text-xl font-semibold text-default-900 ">
|
||||
DashCode
|
||||
</h1> */}
|
||||
</div>
|
||||
<span className=" inline-flex gap-1 items-center">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import DashCodeLogo from "./dascode-logo";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useConfig } from "@/hooks/use-config";
|
||||
import { useMenuHoverConfig } from "@/hooks/use-menu-hover";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
|
||||
const Logo = () => {
|
||||
const [config] = useConfig();
|
||||
const [hoverConfig] = useMenuHoverConfig();
|
||||
const { hovered } = hoverConfig;
|
||||
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
||||
|
||||
if (config.sidebar === "compact") {
|
||||
return (
|
||||
<Link
|
||||
href="/dashboard/analytics"
|
||||
className="flex gap-2 items-center justify-center "
|
||||
>
|
||||
<DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
if (config.sidebar === "two-column" || !isDesktop) return null;
|
||||
|
||||
return (
|
||||
<Link href="/" className="flex items-center">
|
||||
{/* <DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" />
|
||||
{(!config?.collapsed || hovered) && (
|
||||
<h1 className="text-xl font-semibold text-default-900 ">D</h1>
|
||||
)} */}
|
||||
<img
|
||||
className="w-100"
|
||||
src="/../images/all-img/mediahub-logo.png"
|
||||
alt="logo"
|
||||
width={150}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
import Cookies from "js-cookie";
|
||||
import React, { Component } from "react";
|
||||
import Geocode from "react-geocode";
|
||||
import Autocomplete from "react-google-autocomplete";
|
||||
import {
|
||||
GoogleMap,
|
||||
InfoWindow,
|
||||
Marker,
|
||||
withGoogleMap,
|
||||
withScriptjs,
|
||||
} from "react-google-maps";
|
||||
import { GoogleMapsAPI } from "./client-config";
|
||||
|
||||
Geocode.setApiKey(GoogleMapsAPI);
|
||||
Geocode.enableDebug();
|
||||
|
||||
class Map extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
address: "",
|
||||
city: "",
|
||||
area: "",
|
||||
state: "",
|
||||
mapPosition: {
|
||||
lat: this.props.center.lat,
|
||||
lng: this.props.center.lng,
|
||||
},
|
||||
markerPosition: {
|
||||
lat: this.props.center.lat,
|
||||
lng: this.props.center.lng,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current address from the default map position and set those values in the state
|
||||
*/
|
||||
componentDidMount() {
|
||||
Geocode.fromLatLng(
|
||||
this.state.mapPosition.lat,
|
||||
this.state.mapPosition.lng,
|
||||
).then(
|
||||
(response) => {
|
||||
const address = response.results[0].formatted_address;
|
||||
const addressArray = response.results[0].address_components;
|
||||
const city = this.getCity(addressArray);
|
||||
const area = this.getArea(addressArray);
|
||||
const state = this.getState(addressArray);
|
||||
|
||||
console.log("city", city, area, state);
|
||||
|
||||
this.setState({
|
||||
address: address || "",
|
||||
area: area || "",
|
||||
city: city || "",
|
||||
state: state || "",
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
console.error(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component should only update ( meaning re-render ), when the user selects the address, or drags the pin
|
||||
*
|
||||
* @param nextProps
|
||||
* @param nextState
|
||||
* @return {boolean}
|
||||
*/
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (
|
||||
this.state.markerPosition.lat !== this.props.center.lat ||
|
||||
this.state.address !== nextState.address ||
|
||||
this.state.city !== nextState.city ||
|
||||
this.state.area !== nextState.area ||
|
||||
this.state.state !== nextState.state
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.props.center.lat == nextProps.center.lat) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the city and set the city input value to the one selected
|
||||
*
|
||||
* @param addressArray
|
||||
* @return {string}
|
||||
*/
|
||||
getCity = (addressArray) => {
|
||||
let city = "";
|
||||
|
||||
for (const element of addressArray) {
|
||||
if (
|
||||
element.types[0] &&
|
||||
element.types[0] == "administrative_area_level_2"
|
||||
) {
|
||||
city = element.long_name;
|
||||
return city;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Get the area and set the area input value to the one selected
|
||||
*
|
||||
* @param addressArray
|
||||
* @return {string}
|
||||
*/
|
||||
getArea = (addressArray) => {
|
||||
let area = "";
|
||||
|
||||
for (const element of addressArray) {
|
||||
if (element.types[0]) {
|
||||
for (let j = 0; j < element.types.length; j++) {
|
||||
if (
|
||||
element.types[j] == "sublocality_level_1" ||
|
||||
element.types[j] == "locality"
|
||||
) {
|
||||
area = element.long_name;
|
||||
return area;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Get the address and set the address input value to the one selected
|
||||
*
|
||||
* @param addressArray
|
||||
* @return {string}
|
||||
*/
|
||||
getState = (addressArray) => {
|
||||
let state = "";
|
||||
|
||||
for (let i = 0; i < addressArray.length; i++) {
|
||||
for (const element of addressArray) {
|
||||
if (
|
||||
element.types[0] &&
|
||||
element.types[0] == "administrative_area_level_1"
|
||||
) {
|
||||
state = element.long_name;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* And function for city,state and address input
|
||||
* @param event
|
||||
*/
|
||||
onChange = (event) => {
|
||||
this.setState({
|
||||
[event.target.name]: event.target.value,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* This Event triggers when the marker window is closed
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
onInfoWindowClose = () => {};
|
||||
/**
|
||||
* When the marker is dragged you get the lat and long using the functions available from event object.
|
||||
* Use geocode to get the address, city, area and state from the lat and lng positions.
|
||||
* And then set those values in the state.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
onMarkerDragEnd = (event) => {
|
||||
const newLat = event.latLng.lat();
|
||||
const newLng = event.latLng.lng();
|
||||
|
||||
Geocode.fromLatLng(newLat, newLng).then(
|
||||
(response) => {
|
||||
const address = response.results[0].formatted_address;
|
||||
const addressArray = response.results[0].address_components;
|
||||
const city = this.getCity(addressArray);
|
||||
const area = this.getArea(addressArray);
|
||||
const state = this.getState(addressArray);
|
||||
|
||||
this.setState({
|
||||
address: address || "",
|
||||
area: area || "",
|
||||
city: city || "",
|
||||
state: state || "",
|
||||
markerPosition: {
|
||||
lat: newLat,
|
||||
lng: newLng,
|
||||
},
|
||||
mapPosition: {
|
||||
lat: newLat,
|
||||
lng: newLng,
|
||||
},
|
||||
});
|
||||
Cookies.set("map_lat", `${newLat}`, {
|
||||
expires: 1
|
||||
});
|
||||
Cookies.set("map_long", `${newLng}`, {
|
||||
expires: 1
|
||||
});
|
||||
$(".input-location-schedule").val(address);
|
||||
},
|
||||
(error) => {
|
||||
console.error(error);
|
||||
},
|
||||
);
|
||||
};
|
||||
/**
|
||||
* When the user types an address in the search box
|
||||
* @param place
|
||||
*/
|
||||
onPlaceSelected = (place) => {
|
||||
console.log("plc", place);
|
||||
const address = place.formatted_address;
|
||||
const addressArray = place.address_components;
|
||||
const city = this.getCity(addressArray);
|
||||
const area = this.getArea(addressArray);
|
||||
const state = this.getState(addressArray);
|
||||
const latValue = place.geometry.location.lat();
|
||||
const lngValue = place.geometry.location.lng();
|
||||
|
||||
// Set these values in the state.
|
||||
this.setState({
|
||||
address: address || "",
|
||||
area: area || "",
|
||||
city: city || "",
|
||||
state: state || "",
|
||||
markerPosition: {
|
||||
lat: latValue,
|
||||
lng: lngValue,
|
||||
},
|
||||
mapPosition: {
|
||||
lat: latValue,
|
||||
lng: lngValue,
|
||||
},
|
||||
});
|
||||
Cookies.set("map_lat", `${latValue}`, {
|
||||
expires: 1
|
||||
});
|
||||
Cookies.set("map_long", `${lngValue}`, {
|
||||
expires: 1
|
||||
});
|
||||
$(".input-location-schedule").val(address);
|
||||
};
|
||||
render() {
|
||||
const AsyncMap = withScriptjs(
|
||||
withGoogleMap(() => (
|
||||
<GoogleMap
|
||||
google={this.props.google}
|
||||
defaultZoom={this.props.zoom}
|
||||
defaultCenter={{
|
||||
lat: this.state.mapPosition.lat,
|
||||
lng: this.state.mapPosition.lng,
|
||||
}}
|
||||
>
|
||||
<Autocomplete
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "40px",
|
||||
paddingLeft: "16px",
|
||||
marginTop: "2px",
|
||||
marginBottom: "500px",
|
||||
}}
|
||||
onPlaceSelected={this.onPlaceSelected}
|
||||
options={{
|
||||
types: ["geocode"],
|
||||
}}
|
||||
/>
|
||||
{/* InfoWindow on top of marker */}
|
||||
<InfoWindow
|
||||
onClose={this.onInfoWindowClose}
|
||||
position={{
|
||||
lat: this.state.markerPosition.lat + 0.0018,
|
||||
lng: this.state.markerPosition.lng,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{this.state.address}
|
||||
</span>
|
||||
</div>
|
||||
</InfoWindow>
|
||||
{/* Marker */}
|
||||
<Marker
|
||||
google={this.props.google}
|
||||
name="Dolores park"
|
||||
draggable={this.props.draggable}
|
||||
onDragEnd={this.onMarkerDragEnd}
|
||||
position={{
|
||||
lat: this.state.markerPosition.lat,
|
||||
lng: this.state.markerPosition.lng,
|
||||
}}
|
||||
/>
|
||||
<Marker />
|
||||
{/* For Auto complete Search Box */}
|
||||
</GoogleMap>
|
||||
)),
|
||||
);
|
||||
|
||||
let map;
|
||||
|
||||
if (this.props.center.lat == undefined) {
|
||||
map = (
|
||||
<div
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
map = (
|
||||
<div>
|
||||
{/* <div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="">Address</label>
|
||||
<input type="text" name="address" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.address }/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="">All Data</label>
|
||||
<input type="text" name="address" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.markerPosition.lat + ";" + this.state.markerPosition.lng }/>
|
||||
</div>
|
||||
</div> */}
|
||||
<AsyncMap
|
||||
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${GoogleMapsAPI}&libraries=places`}
|
||||
loadingElement={
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
containerElement={
|
||||
<div
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
mapElement={
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
export default Map;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react";
|
||||
import Geocode from "react-geocode";
|
||||
import { GoogleMap, Marker, useLoadScript } from "@react-google-maps/api";
|
||||
import { GoogleMapsAPI } from "./client-config";
|
||||
|
||||
Geocode.setApiKey(GoogleMapsAPI);
|
||||
|
||||
interface MapProps {
|
||||
lat: number;
|
||||
lng: number;
|
||||
draggable?: boolean;
|
||||
onLocationChange?: (location: string) => void; // Tambahkan onLocationChange
|
||||
}
|
||||
|
||||
function Map({ lat, lng, draggable, onLocationChange }: MapProps) {
|
||||
const containerStyle = { width: "100%", height: "400px" };
|
||||
const [selected, setSelected] = React.useState<{
|
||||
lat: number;
|
||||
lng: number;
|
||||
} | null>(null);
|
||||
|
||||
const onMarkerDragEnd = async (e: google.maps.MapMouseEvent) => {
|
||||
const lat = e.latLng?.lat() ?? 0;
|
||||
const lng = e.latLng?.lng() ?? 0;
|
||||
|
||||
try {
|
||||
const response = await Geocode.fromLatLng(lat.toString(), lng.toString());
|
||||
const address = response.results[0].formatted_address;
|
||||
|
||||
setSelected({ lat, lng });
|
||||
if (onLocationChange) {
|
||||
onLocationChange(address); // Panggil callback jika tersedia
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GoogleMap
|
||||
zoom={10}
|
||||
center={selected || { lat, lng }}
|
||||
mapContainerStyle={containerStyle}
|
||||
>
|
||||
<Marker
|
||||
draggable={draggable}
|
||||
position={selected || { lat, lng }}
|
||||
onDragEnd={onMarkerDragEnd}
|
||||
/>
|
||||
</GoogleMap>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import React, { Component } from "react";
|
||||
import Places from "./Maps";
|
||||
interface MapHomeProps {
|
||||
newLat?: any;
|
||||
newLng?: any;
|
||||
draggable?: boolean;
|
||||
setLocation: (location: string) => void;
|
||||
}
|
||||
|
||||
class MapHome extends Component<MapHomeProps> {
|
||||
render() {
|
||||
const { newLat, newLng, draggable, setLocation } = this.props;
|
||||
|
||||
const lat = newLat || -6.2393033;
|
||||
const lng = newLng || 106.8013579;
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: "10px", marginTop: "8px" }}>
|
||||
<Places
|
||||
center={{ lat: Number(lat), lng: Number(lng) }}
|
||||
draggable={draggable}
|
||||
onLocationChange={setLocation}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MapHome;
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
import { GoogleMap, Marker, useLoadScript } from "@react-google-maps/api";
|
||||
import Cookies from "js-cookie";
|
||||
import { useEffect, useState } from "react";
|
||||
import usePlacesAutocomplete, {
|
||||
getGeocode,
|
||||
getLatLng,
|
||||
} from "use-places-autocomplete";
|
||||
import { GoogleMapsAPI } from "./client-config";
|
||||
import Geocode from "react-geocode";
|
||||
import { PlacesCombobox } from "@/components/ui/combobox";
|
||||
|
||||
Geocode.setApiKey(GoogleMapsAPI);
|
||||
|
||||
export default function Places(props: {
|
||||
center: { lat: number; lng: number };
|
||||
draggable?: boolean;
|
||||
onLocationChange?: (location: string) => void;
|
||||
}) {
|
||||
const { isLoaded } = useLoadScript({
|
||||
googleMapsApiKey: GoogleMapsAPI,
|
||||
libraries: ["places"],
|
||||
language: "id",
|
||||
});
|
||||
|
||||
const { center, draggable, onLocationChange } = props;
|
||||
|
||||
if (!isLoaded) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<Map
|
||||
lat={center.lat}
|
||||
lng={center.lng}
|
||||
draggable={draggable}
|
||||
onLocationChange={onLocationChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface MapProps {
|
||||
lat: number;
|
||||
lng: number;
|
||||
draggable?: boolean;
|
||||
onLocationChange?: (location: string) => void;
|
||||
}
|
||||
|
||||
function Map(props: MapProps) {
|
||||
const containerStyle = {
|
||||
width: "100%",
|
||||
height: "400px",
|
||||
};
|
||||
|
||||
const center = {
|
||||
lat: -6.1754,
|
||||
lng: 106.8272,
|
||||
};
|
||||
|
||||
const [selected, setSelected] = useState<{ lat: number; lng: number } | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const { lat, lng, draggable, onLocationChange } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (lat !== undefined && lng !== undefined) {
|
||||
setSelected({ lat, lng });
|
||||
getAddressFromLatLong(lat, lng);
|
||||
}
|
||||
}, [lat, lng]);
|
||||
|
||||
const onMarkerDragEnd = (e: google.maps.MapMouseEvent) => {
|
||||
const lat = e.latLng?.lat() ?? 0;
|
||||
const lng = e.latLng?.lng() ?? 0;
|
||||
console.log(lat, lng);
|
||||
getAddressFromLatLong(lat, lng);
|
||||
if (onLocationChange) {
|
||||
onLocationChange(`Latitude: ${lat}, Longitude: ${lng}`);
|
||||
}
|
||||
};
|
||||
|
||||
async function getAddressFromLatLong(lat: number, lng: number) {
|
||||
try {
|
||||
const response = await Geocode.fromLatLng(lat.toString(), lng.toString());
|
||||
const address = response.results[0].formatted_address;
|
||||
Cookies.set("map_lat", `${lat}`, { expires: 1 });
|
||||
Cookies.set("map_long", `${lng}`, { expires: 1 });
|
||||
console.log("Address:", address);
|
||||
if (onLocationChange) {
|
||||
onLocationChange(address);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<PlacesAutocomplete setSelected={setSelected} />
|
||||
</div>
|
||||
<GoogleMap
|
||||
zoom={selected == null ? 10 : 15}
|
||||
center={selected == null ? center : selected}
|
||||
mapContainerStyle={containerStyle}
|
||||
>
|
||||
{selected && (
|
||||
<Marker
|
||||
draggable={draggable}
|
||||
position={selected}
|
||||
onDragEnd={onMarkerDragEnd}
|
||||
/>
|
||||
)}
|
||||
</GoogleMap>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface PlacesAutocompleteProps {
|
||||
setSelected: (coords: { lat: number; lng: number }) => void;
|
||||
}
|
||||
|
||||
function PlacesAutocomplete({ setSelected }: PlacesAutocompleteProps) {
|
||||
const {
|
||||
ready,
|
||||
value,
|
||||
setValue,
|
||||
suggestions: { status, data },
|
||||
clearSuggestions,
|
||||
} = usePlacesAutocomplete();
|
||||
|
||||
const handleSelect = async (address: string) => {
|
||||
setValue(address, false);
|
||||
clearSuggestions();
|
||||
|
||||
try {
|
||||
const results = await getGeocode({ address });
|
||||
const { lat, lng } = await getLatLng(results[0]);
|
||||
|
||||
setSelected({ lat, lng });
|
||||
console.log("Selected Lat/Lng:", { lat, lng });
|
||||
Cookies.set("map_lat", `${lat}`, { expires: 1 });
|
||||
Cookies.set("map_long", `${lng}`, { expires: 1 });
|
||||
} catch (error) {
|
||||
console.error("Error fetching coordinates:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PlacesCombobox
|
||||
value={value}
|
||||
onValueChange={(newValue) => setValue(newValue)}
|
||||
onSelect={handleSelect}
|
||||
suggestions={data}
|
||||
status={status}
|
||||
disabled={!ready}
|
||||
placeholder="Cari Alamat"
|
||||
className="border"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const GoogleMapsAPI = "AIzaSyCuQHorDceMCzlSgrB9AEY5ns8KeriFsME";
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
'use client'
|
||||
import React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Icon } from '@/components/ui/icon'
|
||||
import { Button } from "@/components/ui/button"
|
||||
interface navProps {
|
||||
dotStyle?: boolean
|
||||
links: {
|
||||
title: string
|
||||
href?: string
|
||||
active: boolean
|
||||
label?: string
|
||||
icon?: any
|
||||
}[]
|
||||
}
|
||||
const Nav = ({ links, dotStyle = false }: navProps) => {
|
||||
if (dotStyle) {
|
||||
return (
|
||||
<nav className=' space-y-1 px-5'>
|
||||
{links.map(({ icon, title, active }, index) =>
|
||||
<Button
|
||||
key={`link-${index}`}
|
||||
fullWidth className='capitalize justify-start hover:bg-transparent hover:text-default-600'
|
||||
variant="ghost">
|
||||
<span
|
||||
className={cn(
|
||||
"h-2 w-2 me-2 rounded-full transition-all duration-150 ring-0 bg-default ring-default ring-opacity-30",
|
||||
{
|
||||
"ring-4": active,
|
||||
"bg-destructive ring-destructive": title === 'team' || title==="promotions",
|
||||
"bg-success ring-success": title === 'low' || title==="social",
|
||||
"bg-warning ring-warning": title === 'medium',
|
||||
"bg-primary ring-primary": title === 'high' || title==="buisness",
|
||||
"bg-info ring-info/30": title === 'update',
|
||||
}
|
||||
)}
|
||||
></span>
|
||||
{title}
|
||||
</Button>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<nav className=' space-y-1 px-5'>
|
||||
|
||||
{links.map(({ icon, title, active }, index) =>
|
||||
<Button key={`link-${index}`} color='secondary' fullWidth className='hover:ring-0 hover:ring-transparent justify-start' variant={active ? 'default' : 'ghost'}>
|
||||
<Icon
|
||||
icon={icon}
|
||||
className='me-2 h-5 w-5'
|
||||
/>
|
||||
|
||||
{title}
|
||||
</Button>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default Nav
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
// import {createSharedPathnamesNavigation} from 'next-intl/navigation';
|
||||
// import {locales} from '@/config';
|
||||
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
|
||||
import { locales } from '@/config';
|
||||
|
||||
// export const {Link, redirect, usePathname, useRouter} =
|
||||
// createSharedPathnamesNavigation({locales,});
|
||||
export const { Link, redirect, usePathname, useRouter } =
|
||||
createSharedPathnamesNavigation({
|
||||
locales: locales,
|
||||
defaultLocale: "in",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,269 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DateRangePicker from "@/components/date-range-picker";
|
||||
import { usePathname } from "@/components/navigation";
|
||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "./ui/dialog";
|
||||
import { Button } from "./ui/button";
|
||||
import { Label } from "./ui/label";
|
||||
import { Input } from "./ui/input";
|
||||
import pdfGenerator from "@/utils/pdf-generator";
|
||||
import {
|
||||
tableauSignin,
|
||||
tableauViewImage,
|
||||
} from "@/service/tableau/tableau-service";
|
||||
|
||||
const PageTitle = ({
|
||||
title,
|
||||
className,
|
||||
}: {
|
||||
title?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [reportDate, setReportDate] = useState<any>();
|
||||
const pathname = usePathname();
|
||||
const name = pathname?.split("/").slice(1).join(" ");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
useEffect(() => {
|
||||
console.log("role", roleId);
|
||||
}, [roleId]);
|
||||
|
||||
const downloadReport = async () => {
|
||||
console.log(reportDate);
|
||||
const formattedDate = `${reportDate.year}-${String(
|
||||
reportDate.month
|
||||
).padStart(2, "0")}-${String(reportDate.day).padStart(2, "0")}`;
|
||||
|
||||
const resLogin = await tableauSignin();
|
||||
const token = resLogin?.data?.data?.credentials?.token;
|
||||
|
||||
const resFrontCover = await tableauViewImage(
|
||||
token,
|
||||
"5b35ec9c-add5-45e7-b71a-31a28db9954d",
|
||||
reportDate
|
||||
);
|
||||
const resAssignmentDetail = await tableauViewImage(
|
||||
token,
|
||||
"b2e7e184-8311-49e1-8314-a6b2d8a8c6f8",
|
||||
reportDate
|
||||
);
|
||||
// const resMonitoringContent = await tableauViewImage(
|
||||
// token,
|
||||
// "34683f52-1029-49e4-950d-2c94159ebd8f",
|
||||
// reportDate
|
||||
// );
|
||||
// const resMonitoringArticle = await tableauViewImage(
|
||||
// token,
|
||||
// "34683f52-1029-49e4-950d-2c94159ebd8f",
|
||||
// reportDate
|
||||
// );
|
||||
const resInteractionContent = await tableauViewImage(
|
||||
token,
|
||||
"8f038669-4135-4693-96e5-e6a957a1cc4f",
|
||||
reportDate
|
||||
);
|
||||
const resTotalInteractionContent = await tableauViewImage(
|
||||
token,
|
||||
"e019e7f7-8d3b-4303-aa20-3fdbb318df1e",
|
||||
reportDate
|
||||
);
|
||||
const resInteractionContentCategory1 = await tableauViewImage(
|
||||
token,
|
||||
"c1dcc6c8-17dc-4e30-85db-7938e30b4418",
|
||||
reportDate
|
||||
);
|
||||
const resInteractionContentCategory2 = await tableauViewImage(
|
||||
token,
|
||||
"68b95c1d-aee1-4073-afb7-05188eccee67",
|
||||
reportDate
|
||||
);
|
||||
const resInteractionContentCategory3 = await tableauViewImage(
|
||||
token,
|
||||
"7de1f07f-9a09-45a8-8ff6-96484106492d",
|
||||
reportDate
|
||||
);
|
||||
const resInteractionDistribution = await tableauViewImage(
|
||||
token,
|
||||
"0781d7e6-e133-416c-bf41-e5b2fa5996dd",
|
||||
reportDate
|
||||
);
|
||||
const resAssignments = await tableauViewImage(
|
||||
token,
|
||||
"b2e7e184-8311-49e1-8314-a6b2d8a8c6f8",
|
||||
reportDate
|
||||
);
|
||||
const resUserCount = await tableauViewImage(
|
||||
token,
|
||||
"36ff675d-790c-4420-b0c7-0a6441f59cb1",
|
||||
reportDate
|
||||
);
|
||||
const resUserCountDistribution = await tableauViewImage(
|
||||
token,
|
||||
"86370e20-b13d-4cb0-a54a-c25dc13bb427",
|
||||
reportDate
|
||||
);
|
||||
const resBackCover = await tableauViewImage(
|
||||
token,
|
||||
"516c8790-ccd5-44dc-bb66-b0e146d7168b",
|
||||
reportDate
|
||||
);
|
||||
const blobFrontCover = new Blob([resFrontCover.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
const blobAssignmentDetail = new Blob([resAssignmentDetail.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
// const blobMonitoringContent = new Blob([resMonitoringContent.data], { type: "image/png" });
|
||||
// const blobMonitoringArticle = new Blob([resMonitoringArticle.data], { type: "image/png" });
|
||||
const blobInteractionContent = new Blob([resInteractionContent.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
const blobTotalInteractionContent = new Blob(
|
||||
[resTotalInteractionContent.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionContentCategory1 = new Blob(
|
||||
[resInteractionContentCategory1.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionContentCategory2 = new Blob(
|
||||
[resInteractionContentCategory2.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionContentCategory3 = new Blob(
|
||||
[resInteractionContentCategory3.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionDistribution = new Blob(
|
||||
[resInteractionDistribution.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobAssignments = new Blob([resAssignments.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
const blobUserCount = new Blob([resUserCount.data], { type: "image/png" });
|
||||
const blobUserCountDistribution = new Blob(
|
||||
[resUserCountDistribution.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobBackCover = new Blob([resBackCover.data], { type: "image/png" });
|
||||
|
||||
console.log(blobFrontCover);
|
||||
|
||||
await pdfGenerator([
|
||||
blobFrontCover,
|
||||
blobAssignmentDetail,
|
||||
// blobMonitoringContent,
|
||||
// blobMonitoringArticle,
|
||||
blobInteractionContent,
|
||||
blobTotalInteractionContent,
|
||||
blobInteractionContentCategory1,
|
||||
blobInteractionContentCategory2,
|
||||
blobInteractionContentCategory3,
|
||||
blobInteractionDistribution,
|
||||
blobAssignments,
|
||||
blobUserCount,
|
||||
blobUserCountDistribution,
|
||||
blobBackCover,
|
||||
]);
|
||||
};
|
||||
|
||||
async function mergeImagesToCanvas(blobs: Blob[]) {
|
||||
try {
|
||||
const images = await Promise.all(
|
||||
blobs.map((blob) => {
|
||||
return new Promise<HTMLImageElement>((resolve) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
resolve(img);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Hitung total tinggi dari semua gambar (stacked vertically)
|
||||
const width = Math.max(...images.map((img) => img.width));
|
||||
const height = images.reduce((sum, img) => sum + img.height, 0);
|
||||
|
||||
const canvas = document.getElementById("pdf-canvas") as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
let y = 0;
|
||||
for (const img of images) {
|
||||
ctx.drawImage(img, 0, y, img.width, img.height);
|
||||
y += img.height;
|
||||
}
|
||||
|
||||
// Simpan hasil sebagai gambar (PNG) atau bisa print
|
||||
const dataURL = canvas.toDataURL("image/png");
|
||||
|
||||
// Simpan atau cetak (print sebagai PDF)
|
||||
const link = document.createElement("a");
|
||||
link.href = dataURL;
|
||||
link.download = "merged-image.png";
|
||||
link.click();
|
||||
|
||||
// Atau tampilkan dan user bisa "Print to PDF"
|
||||
window.open(dataURL, "_blank");
|
||||
} catch (error) {
|
||||
console.log("Error :::", error);
|
||||
}
|
||||
}
|
||||
|
||||
return Number(roleId) == 2 || Number(roleId) == 11 || Number(roleId) == 12 ? (
|
||||
""
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-wrap gap-4 items-center justify-between",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="text-2xl font-medium text-default-800 capitalize">
|
||||
Dashboard
|
||||
</div>
|
||||
{/* <Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Download Report</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Download Report</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="w-full">
|
||||
<canvas id="pdf-canvas"></canvas>
|
||||
<Label>Date</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={reportDate}
|
||||
onChange={(e) => setReportDate(e.target.value)}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" onClick={downloadReport}>
|
||||
Download
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
const Copyright = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
return <>Copyright {currentYear}, Dashcode All Rights Reserved.</>;
|
||||
};
|
||||
|
||||
export default Copyright;
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
"use client";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
type Inputs = {
|
||||
example: string;
|
||||
exampleRequired: string;
|
||||
};
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { error, loading } from "@/config/swal";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { forgotPassword } from "@/service/landing/landing";
|
||||
|
||||
const ForgotPass = () => {
|
||||
const [username, setUsername] = useState<any>();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>();
|
||||
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
|
||||
|
||||
async function handleCheckUsername() {
|
||||
loading();
|
||||
const response = await forgotPassword(username);
|
||||
|
||||
if (response.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
successSubmit();
|
||||
return false;
|
||||
}
|
||||
|
||||
function successSubmit() {
|
||||
MySwal.fire({
|
||||
title: "Email berhasil dikirim. Silahkan cek email Anda.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result: any) => {
|
||||
if (result.isConfirmed) {
|
||||
router.push("/admin");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 ">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="user-name">Username</Label>
|
||||
<Input id="user-name" defaultValue="akun1234" className="h-[48px] text-sm text-default-900 " onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<Button type="submit" fullWidth onClick={handleCheckUsername}>
|
||||
Check Username
|
||||
</Button>
|
||||
<Button type="submit" fullWidth onClick={handleCheckUsername}>
|
||||
Kirim Ulang?{" "}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPass;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
|
||||
type Inputs = {
|
||||
example: string;
|
||||
exampleRequired: string;
|
||||
};
|
||||
|
||||
const LockScreen = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>();
|
||||
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
|
||||
console.log(watch("example"));
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 ">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
defaultValue="dashcode@gmail.com"
|
||||
{...register("example")}
|
||||
className="h-[48px] text-sm text-default-900 "
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" fullWidth>
|
||||
Unlock
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
export default LockScreen;
|
||||
|
|
@ -0,0 +1,652 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import Cookies from "js-cookie";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { cn, getCookiesDecrypt, setCookiesEncrypt } from "@/lib/utils";
|
||||
import { Eye, EyeOff, Loader2 } from "lucide-react";
|
||||
import {
|
||||
getProfile,
|
||||
login,
|
||||
postEmailValidation,
|
||||
postSetupEmail,
|
||||
requestOTP,
|
||||
verifyOTPByUsername,
|
||||
} from "@/service/auth";
|
||||
import { toast } from "sonner";
|
||||
import { useRouter } from "@/components/navigation";
|
||||
import { warning } from "@/lib/swal";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSeparator,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { error, loading } from "@/config/swal";
|
||||
import { data } from "jquery";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { getUserNotifications, listRole } from "@/service/landing/landing";
|
||||
|
||||
// Schema validasi menggunakan zod
|
||||
const schema = z.object({
|
||||
username: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
password: z
|
||||
.string()
|
||||
.min(4, { message: "Password must be at least 4 characters." }),
|
||||
});
|
||||
|
||||
// Tipe untuk form values
|
||||
type LoginFormValues = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const LoginForm = () => {
|
||||
const [isPending, startTransition] = React.useTransition();
|
||||
const router = useRouter();
|
||||
const [passwordType, setPasswordType] = React.useState("password");
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
const [step, setStep] = useState<number>(1);
|
||||
const [otpValue, setOtpValue] = useState("");
|
||||
const [userIdentity] = useState();
|
||||
const [email, setEmail] = useState();
|
||||
const [category, setCategory] = useState("5");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [oldEmail, setOldEmail] = useState("");
|
||||
const [oldEmailValidate, setOldEmailValidate] = useState("");
|
||||
const [role, setRole] = useState<any>();
|
||||
const [menuActive, setMenuActive] = useState<string>();
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [notificationsUpdate, setNotificationsUpdate] = useState([]);
|
||||
const [newEmail, setNewEmail] = useState("");
|
||||
const [newEmailValidate, setNewEmailValidate] = useState("");
|
||||
const [otpValidate, setOtpValidate] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const togglePasswordType = () => {
|
||||
setPasswordType((prevType) =>
|
||||
prevType === "password" ? "text" : "password"
|
||||
);
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
} = useForm<LoginFormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const { key } = event;
|
||||
const target = event.currentTarget;
|
||||
|
||||
if (key === "Enter") {
|
||||
event.preventDefault();
|
||||
|
||||
const inputs = Array.from(target.form?.querySelectorAll("input") || []);
|
||||
const currentIndex = inputs.indexOf(target);
|
||||
const nextInput = inputs[currentIndex + 1] as HTMLElement | undefined;
|
||||
|
||||
if (nextInput) {
|
||||
nextInput.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit: SubmitHandler<LoginFormValues> = async (data) => {
|
||||
try {
|
||||
const response = await login({
|
||||
...data,
|
||||
grantType: "password",
|
||||
clientId: "mediahub-app",
|
||||
});
|
||||
|
||||
console.log("LOGIN: ", response);
|
||||
|
||||
if (response?.error) {
|
||||
toast.error("Username / Password Tidak Sesuai");
|
||||
} else {
|
||||
const { access_token } = response?.data;
|
||||
const { refresh_token } = response?.data;
|
||||
const dateTime = new Date();
|
||||
const newTime = dateTime.getTime() + 10 * 60 * 1000;
|
||||
|
||||
Cookies.set("access_token", access_token, {
|
||||
expires: 1,
|
||||
});
|
||||
Cookies.set("refresh_token", refresh_token, {
|
||||
expires: 1,
|
||||
});
|
||||
Cookies.set("time_refresh", new Date(newTime).toISOString(), {
|
||||
expires: 1,
|
||||
});
|
||||
|
||||
Cookies.set("is_first_login", String(true), {
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
});
|
||||
const profile = await getProfile(access_token);
|
||||
console.log("PROFILE : ", profile?.data?.data);
|
||||
|
||||
Cookies.set("home_path", profile?.data?.data?.homePath, {
|
||||
expires: 1,
|
||||
});
|
||||
Cookies.set(
|
||||
"profile_picture",
|
||||
profile?.data?.data?.profilePictureUrl,
|
||||
{
|
||||
expires: 1,
|
||||
}
|
||||
);
|
||||
Cookies.set("state", profile?.data?.data?.userLevel?.name, {
|
||||
expires: 1,
|
||||
});
|
||||
Cookies.set(
|
||||
"state-prov",
|
||||
profile.data?.data?.userLevel?.province?.provName,
|
||||
{
|
||||
expires: 1,
|
||||
}
|
||||
);
|
||||
setCookiesEncrypt("uie", profile?.data?.data?.id, {
|
||||
expires: 1,
|
||||
});
|
||||
setCookiesEncrypt("urie", profile?.data?.data?.roleId, {
|
||||
expires: 1,
|
||||
});
|
||||
setCookiesEncrypt("urne", profile?.data?.data?.role?.name, {
|
||||
expires: 1,
|
||||
});
|
||||
setCookiesEncrypt("ulie", profile?.data?.data?.userLevel?.id, {
|
||||
expires: 1,
|
||||
});
|
||||
setCookiesEncrypt(
|
||||
"uplie",
|
||||
profile?.data?.data?.userLevel?.parentLevelId,
|
||||
{
|
||||
expires: 1,
|
||||
}
|
||||
);
|
||||
setCookiesEncrypt(
|
||||
"ulne",
|
||||
profile?.data?.data?.userLevel?.levelNumber,
|
||||
{
|
||||
expires: 1,
|
||||
}
|
||||
);
|
||||
setCookiesEncrypt("ufne", profile?.data?.data?.fullname, {
|
||||
expires: 1,
|
||||
});
|
||||
setCookiesEncrypt("ulnae", profile?.data?.data?.userLevel?.name, {
|
||||
expires: 1,
|
||||
});
|
||||
setCookiesEncrypt("uinse", profile?.data?.data?.instituteId, {
|
||||
expires: 1,
|
||||
});
|
||||
console.log("ssaddd", profile?.data?.data?.roleId);
|
||||
if (
|
||||
Number(profile?.data?.data?.roleId) == 2 ||
|
||||
Number(profile?.data?.data?.roleId) == 3 ||
|
||||
Number(profile?.data?.data?.roleId) == 4 ||
|
||||
Number(profile?.data?.data?.roleId) == 9 ||
|
||||
Number(profile?.data?.data?.roleId) == 10 ||
|
||||
Number(profile?.data?.data?.roleId) == 11 ||
|
||||
Number(profile?.data?.data?.roleId) == 12 ||
|
||||
Number(profile?.data?.data?.roleId) == 18 ||
|
||||
Number(profile?.data?.data?.roleId) == 19
|
||||
) {
|
||||
if (profile?.data?.data?.roleId === 18) {
|
||||
window.location.href = "/in/dashboard/executive-data";
|
||||
// router.push('/admin/dashboard');
|
||||
Cookies.set("status", "login", {
|
||||
expires: 1,
|
||||
});
|
||||
} else if (profile?.data?.data?.roleId === 2) {
|
||||
window.location.href = "/in/dashboard/executive";
|
||||
Cookies.set("status", "login", {
|
||||
expires: 1,
|
||||
});
|
||||
} else if (
|
||||
profile?.data?.data?.userLevel?.id == 794 ||
|
||||
profile?.data?.data?.userLevel?.parentLevelId == 761
|
||||
) {
|
||||
window.location.href = "/in/dashboard";
|
||||
Cookies.set("status", "login", {
|
||||
expires: 1,
|
||||
});
|
||||
} else {
|
||||
window.location.href = "/in/dashboard";
|
||||
// router.push('/admin/dashboard');
|
||||
Cookies.set("status", "login", {
|
||||
expires: 1,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
Cookies.set("status", "login", {
|
||||
expires: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || "An unexpected error occurred.");
|
||||
}
|
||||
// startTransition( () => {
|
||||
|
||||
// });
|
||||
};
|
||||
|
||||
const handleEmailValidation = async () => {
|
||||
const data = getValues();
|
||||
|
||||
// loading();
|
||||
const response = await postEmailValidation(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response?.message);
|
||||
return false;
|
||||
}
|
||||
const msg = response?.data?.message;
|
||||
if (msg == "Continue to setup email") {
|
||||
setStep(2);
|
||||
} else if (msg == "Email is valid and OTP has been sent") {
|
||||
setStep(3);
|
||||
} else if (msg == "Username & password valid") {
|
||||
onSubmit(data);
|
||||
} else {
|
||||
setStep(1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSetupEmail = async () => {
|
||||
const values = getValues();
|
||||
const data = {
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
oldEmail: oldEmail,
|
||||
newEmail: newEmail,
|
||||
};
|
||||
|
||||
// loading();
|
||||
const response = await postSetupEmail(data);
|
||||
// close();
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
const msg = response?.data?.message;
|
||||
if (msg == "Email is valid and OTP has been sent") {
|
||||
setStep(3);
|
||||
} else if (msg == "The old email is not same") {
|
||||
error("Email is invalid");
|
||||
}
|
||||
};
|
||||
|
||||
const checkEmail = (state: any, e: any) => {
|
||||
const regEmail =
|
||||
/^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\dA-Za-z\-]+\.)+[A-Za-z]{2,}))$/;
|
||||
|
||||
if (regEmail.test(e)) {
|
||||
if (state == "old") {
|
||||
setOldEmailValidate("");
|
||||
setOldEmail(e);
|
||||
} else {
|
||||
setNewEmailValidate("");
|
||||
setNewEmail(e);
|
||||
}
|
||||
} else {
|
||||
if (state == "old") {
|
||||
setOldEmailValidate("Email tidak valid");
|
||||
setOldEmail("");
|
||||
} else {
|
||||
setNewEmailValidate("Email tidak valid");
|
||||
setNewEmail("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginOTP = async () => {
|
||||
// const otp = `${otp1}${otp2}${otp3}${otp4}${otp5}${otp6}`;
|
||||
const values = getValues();
|
||||
|
||||
if (otpValue.length === 6) {
|
||||
loading();
|
||||
const response = await verifyOTPByUsername(values.username, otpValue);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
if (response?.message === "success") {
|
||||
onSubmit(values);
|
||||
} else {
|
||||
setOtpValidate("Kode OTP Tidak Valid");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let menu = "";
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
setMenuActive(menu);
|
||||
const res = await listRole();
|
||||
setRole(res?.data?.data);
|
||||
}
|
||||
|
||||
async function getNotif() {
|
||||
if (roleId != undefined) {
|
||||
const response = await getUserNotifications(0, 2);
|
||||
setNotifications(response?.data?.data?.content);
|
||||
console.log("respon:", response);
|
||||
}
|
||||
}
|
||||
|
||||
async function getNotifUpdate() {
|
||||
if (roleId != undefined) {
|
||||
const response = await getUserNotifications(0, 3);
|
||||
setNotificationsUpdate(response?.data?.data?.content);
|
||||
console.log("Notiffff:", response);
|
||||
}
|
||||
}
|
||||
|
||||
initState();
|
||||
getNotif();
|
||||
getNotifUpdate();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{step !== 3 && (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="mt-5 2xl:mt-7 space-y-4"
|
||||
>
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="text-left 2xl:mb-10 mb-4 mt-10">
|
||||
<h4 className="font-semibold text-3xl text-left">
|
||||
{t("logInPlease", { defaultValue: "Log In Please" })}
|
||||
</h4>
|
||||
<div className="text-default-500 text-base">
|
||||
{t("acc", { defaultValue: "Acc" })}
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<span className="w-full lg:w-fit px-2 h-8 text-red-500 hover:cursor-pointer">
|
||||
{t("register", { defaultValue: "Register" })}
|
||||
</span>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="sm" className="sm:max-w-[425px]">
|
||||
<div className="flex flex-col w-full gap-1">
|
||||
<p className="text-lg font-semibold text-center">
|
||||
{t("categoryReg", { defaultValue: "Category Reg" })}
|
||||
</p>
|
||||
<p className="text-base text-center">
|
||||
{t("selectOne", { defaultValue: "Select One" })}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{role?.map((row: any) => (
|
||||
<div key={row.id}>
|
||||
<input
|
||||
type="radio"
|
||||
id={`category${row.id}`}
|
||||
name="category"
|
||||
className=""
|
||||
value={row.id}
|
||||
checked={category == `${row.id}`}
|
||||
onChange={(event) =>
|
||||
setCategory(event.target.value)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
className="ml-2"
|
||||
htmlFor={`category${row.id}`}
|
||||
>
|
||||
{row.name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="border-b-2 border-black"></div>
|
||||
<DialogFooter>
|
||||
<Link
|
||||
href={`/auth/registration?category=${category}`}
|
||||
className="flex justify-center bg-red-500 px-4 py-1 rounded-md border border-black text-white"
|
||||
type="submit"
|
||||
>
|
||||
{t("next", { defaultValue: "Next" })}{" "}
|
||||
</Link>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label
|
||||
htmlFor="username"
|
||||
className="font-medium text-default-600"
|
||||
>
|
||||
Username
|
||||
</Label>
|
||||
<Input
|
||||
size="lg"
|
||||
disabled={isPending}
|
||||
{...register("username")}
|
||||
id="username"
|
||||
type="text"
|
||||
className={cn("", {
|
||||
"border-destructive": errors.username,
|
||||
})}
|
||||
/>
|
||||
{errors.username?.message && (
|
||||
<div className="text-destructive mt-2 text-sm">
|
||||
{errors.username.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3.5 space-y-2">
|
||||
<Label
|
||||
htmlFor="password"
|
||||
className="font-medium text-default-600"
|
||||
>
|
||||
{t("password", { defaultValue: "Password" })}
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
size="lg"
|
||||
disabled={isPending}
|
||||
{...register("password")}
|
||||
id="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
className="peer pr-10"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-default-500 hover:text-default-700"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
{errors.password?.message && (
|
||||
<div className="text-destructive mt-2 text-sm">
|
||||
{errors.password.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox id="checkbox" defaultChecked />
|
||||
<Label htmlFor="checkbox">{t("rememberMe", { defaultValue: "Remember Me" })}</Label>
|
||||
</div>
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium"
|
||||
>
|
||||
{t("forgotPass", { defaultValue: "Forgot Pass" })}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
fullWidth
|
||||
onClick={handleEmailValidation}
|
||||
disabled={isPending}
|
||||
>
|
||||
Selanjutnya
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<>
|
||||
<div className="text-left 2xl:mb-10 mb-4">
|
||||
<h4 className="font-semibold text-3xl text-left">
|
||||
Anda perlu memasukkan email baru untuk bisa Login.
|
||||
</h4>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center mb-6">
|
||||
<div className="space-y-2">
|
||||
<Label
|
||||
htmlFor="username"
|
||||
className="font-medium text-default-600"
|
||||
>
|
||||
Email Lama <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
size="lg"
|
||||
disabled={isPending}
|
||||
onChange={(e) => checkEmail("old", e.target.value)}
|
||||
id="oldEmail"
|
||||
type="email"
|
||||
className={cn("", {
|
||||
"border-destructive": errors.username,
|
||||
})}
|
||||
/>
|
||||
<p className="invalid-feedback-custom">{oldEmailValidate}</p>
|
||||
{errors.username?.message && (
|
||||
<div className="text-destructive mt-2 text-sm">
|
||||
{errors.username.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label
|
||||
htmlFor="username"
|
||||
className="font-medium text-default-600"
|
||||
>
|
||||
Email Baru <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
size="lg"
|
||||
disabled={isPending}
|
||||
onChange={(e) => checkEmail("new", e.target.value)}
|
||||
id="newEmail"
|
||||
type="email"
|
||||
className={cn("", {
|
||||
"border-destructive": errors.username,
|
||||
})}
|
||||
/>
|
||||
<p className="invalid-feedback-custom">{newEmailValidate}</p>
|
||||
{errors.username?.message && (
|
||||
<div className="text-destructive mt-2 text-sm">
|
||||
{errors.username.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
fullWidth
|
||||
className="bg-red-500"
|
||||
onClick={handleSetupEmail}
|
||||
type="button"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
<div className="mt-10 space-y-6">
|
||||
<div className="text-left 2xl:mb-10 mb-4 mt-10">
|
||||
<h4 className="font-semibold text-3xl text-left">
|
||||
{t("pleaseEnterOtp", { defaultValue: "Please Enter Otp" })}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mb-6">
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
onChange={(val: string) => setOtpValue(val)}
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} onKeyDown={handleTypeOTP} />
|
||||
<InputOTPSlot index={1} onKeyDown={handleTypeOTP} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={2} onKeyDown={handleTypeOTP} />
|
||||
<InputOTPSlot index={3} onKeyDown={handleTypeOTP} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={4} onKeyDown={handleTypeOTP} />
|
||||
<InputOTPSlot index={5} onKeyDown={handleTypeOTP} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
|
||||
{otpValidate && (
|
||||
<p className="invalid-feedback-custom text-center">
|
||||
<b>{otpValidate}</b>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
className="bg-red-500"
|
||||
type="button"
|
||||
onClick={handleLoginOTP}
|
||||
disabled={otpValue.length !== 6}
|
||||
>
|
||||
{t("signIn", { defaultValue: "Sign In" })}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
'use client'
|
||||
import Image from 'next/image';
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
const Logo = () => {
|
||||
const { theme: mode } = useTheme();
|
||||
return (
|
||||
<div>
|
||||
<Image
|
||||
src={
|
||||
mode === "light"
|
||||
? "/images/logo/logo.svg"
|
||||
: "/images/logo/logo-white.svg"
|
||||
}
|
||||
alt=""
|
||||
width={300}
|
||||
height={300}
|
||||
className=" w-36 "
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
|
||||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
|
||||
type Inputs = {
|
||||
example: string;
|
||||
exampleRequired: string;
|
||||
};
|
||||
|
||||
const RegForm = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>();
|
||||
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
|
||||
console.log(watch("example"));
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 ">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="John Doe"
|
||||
{...register("example")}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
placeholder="dashcode@gmail.com"
|
||||
{...register("example")}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="dashcode"
|
||||
{...register("exampleRequired", { required: true })}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="checkbox"
|
||||
defaultChecked
|
||||
/>
|
||||
<Label htmlFor="checkbox">
|
||||
You Accept Our Terms And Conditions And Privacy Policy
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" fullWidth>
|
||||
Create An Account
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
export default RegForm;
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import Image from "next/image";
|
||||
|
||||
const Social = ({ locale }: { locale: string }) => {
|
||||
return (
|
||||
<>
|
||||
<ul className="flex">
|
||||
<li className="flex-1">
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex h-10 w-10 p-2 bg-[#1C9CEB] text-white text-2xl flex-col items-center justify-center rounded-full"
|
||||
>
|
||||
<Image
|
||||
width={300}
|
||||
height={300}
|
||||
className="w-full h-full"
|
||||
src="/images/icon/tw.svg"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="flex-1">
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex h-10 w-10 p-2 bg-[#395599] text-white text-2xl flex-col items-center justify-center rounded-full"
|
||||
>
|
||||
<Image
|
||||
width={300}
|
||||
height={300}
|
||||
className="w-full h-full"
|
||||
src="/images/icon/fb.svg"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="flex-1">
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex h-10 w-10 p-2 bg-[#0A63BC] text-white text-2xl flex-col items-center justify-center rounded-full"
|
||||
>
|
||||
<Image
|
||||
width={300}
|
||||
height={300}
|
||||
className="w-full h-full"
|
||||
src="/images/icon/in.svg"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="flex-1">
|
||||
<form
|
||||
// action={async () => {
|
||||
// "use server";
|
||||
// await signIn("google", { redirectTo: `/${locale}/dashboard/analytics` });
|
||||
// }}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex h-10 w-10 p-2 bg-[#EA4335] text-white text-2xl flex-col items-center justify-center rounded-full"
|
||||
>
|
||||
<Image
|
||||
width={300}
|
||||
height={300}
|
||||
className="w-full h-full"
|
||||
src="/images/icon/gp.svg"
|
||||
alt=""
|
||||
/>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Social;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
'use client'
|
||||
import { useTheme } from 'next-themes';
|
||||
import React from 'react'
|
||||
import { Button } from '@/components/ui/button';
|
||||
import Link from 'next/link';
|
||||
import { useMediaQuery } from '@/hooks/use-media-query';
|
||||
|
||||
const BuyButton = () => {
|
||||
const { theme: mode } = useTheme();
|
||||
const isMobile = useMediaQuery("(min-width: 768px)");
|
||||
return (
|
||||
<>
|
||||
<Button asChild size={!isMobile ? "sm" : "md"} fullWidth color={mode !== 'light' ? 'secondary' : 'default'}>
|
||||
<Link href="https://1.envato.market/vNaJR3">Buy Now</Link>
|
||||
</Button>
|
||||
<Button asChild size={!isMobile ? "sm" : "md"} fullWidth color={mode !== 'light' ? 'secondary' : 'default'}>
|
||||
<Link
|
||||
href="https://themeforest.net/user/codeshaperbd/portfolio"
|
||||
target="__blank"
|
||||
>
|
||||
Our Portfolio
|
||||
</Link>
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BuyButton
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue