├── src ├── @types │ ├── iso-date.ts │ ├── base-64-blob.ts │ ├── file.ts │ └── react-barcode-reader.d.ts ├── components │ ├── image-input │ │ ├── index.ts │ │ └── functions │ │ │ └── resize-image.ts │ ├── input-additional-content.tsx │ ├── progress-bar.tsx │ ├── redirect-if-unauthenticated.tsx │ ├── theme-switcher.tsx │ ├── input-controllers │ │ ├── text-input.tsx │ │ └── user-email.tsx │ ├── redirect-if-authenticated.tsx │ ├── bar-chart.tsx │ ├── onboard-card.tsx │ ├── line-chart.tsx │ ├── confirmation-modal.tsx │ ├── stepper.tsx │ └── grid-pattern.tsx ├── app │ ├── favicon.ico │ ├── icon1.png │ ├── icon2.png │ ├── icon3.png │ ├── icon4.png │ ├── apple-icon.png │ ├── (guest) │ │ ├── forgot-password │ │ │ ├── _components │ │ │ │ ├── security-question │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── question-and-answer-field.tsx │ │ │ │ │ └── security-question.tsx │ │ │ │ └── email-form.tsx │ │ │ ├── _stores │ │ │ │ └── security-question.ts │ │ │ └── page.tsx │ │ ├── reset-password │ │ │ ├── _components │ │ │ │ └── reset-password-form │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── _stores │ │ │ │ │ ├── pin-visibility.ts │ │ │ │ │ └── password-visibility.ts │ │ │ │ │ ├── _components │ │ │ │ │ ├── password-field.tsx │ │ │ │ │ ├── confirm-password-field.tsx │ │ │ │ │ ├── pin-field.tsx │ │ │ │ │ └── confirm-pin-field.tsx │ │ │ │ │ └── reset-password-form.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── login │ │ │ └── page.tsx │ │ └── onboarding │ │ │ ├── import-backup │ │ │ └── page.tsx │ │ │ └── page.tsx │ ├── (authenticated user) │ │ ├── data │ │ │ ├── products │ │ │ │ ├── _components │ │ │ │ │ └── product-card │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── _hook.tsx │ │ │ │ │ │ └── product-card.tsx │ │ │ │ ├── (form) │ │ │ │ │ ├── _components │ │ │ │ │ │ ├── product-form │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ ├── icon-button-input-content.tsx │ │ │ │ │ │ │ └── hook.ts │ │ │ │ │ │ └── page-template.tsx │ │ │ │ │ ├── create │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── _hook.ts │ │ │ │ │ └── [uuid] │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── _hook.ts │ │ │ │ ├── _hook.ts │ │ │ │ └── page.tsx │ │ │ └── users │ │ │ │ ├── (form) │ │ │ │ ├── _types │ │ │ │ │ └── form-values.ts │ │ │ │ ├── create │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── hook.ts │ │ │ │ ├── [uuid] │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── hook.ts │ │ │ │ └── _functions │ │ │ │ │ └── get-validated-form-values.ts │ │ │ │ ├── hook.ts │ │ │ │ └── page.tsx │ │ ├── settings │ │ │ ├── database │ │ │ │ ├── export │ │ │ │ │ └── page.tsx │ │ │ │ ├── sync │ │ │ │ │ └── page.tsx │ │ │ │ └── wipe │ │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── reports │ │ │ ├── sale-per-product │ │ │ │ └── page.tsx │ │ │ ├── sale-per-tx │ │ │ │ └── page.tsx │ │ │ ├── stock-in-out-per-product │ │ │ │ └── page.tsx │ │ │ ├── sale-per-product-category │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── purchases │ │ │ ├── layout.ts │ │ │ ├── (form) │ │ │ │ ├── _functions │ │ │ │ │ ├── validate-form-values.ts │ │ │ │ │ └── update-stocks-on-received.ts │ │ │ │ ├── _types │ │ │ │ │ └── form-values.ts │ │ │ │ ├── [uuid] │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── page-hook.ts │ │ │ │ ├── create │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── page-hook.ts │ │ │ │ └── _components │ │ │ │ │ └── costs-input.tsx │ │ │ └── page-hook.ts │ │ ├── layout.tsx │ │ ├── onboarding │ │ │ └── steps │ │ │ │ ├── layout.tsx │ │ │ │ ├── 2-add-products-and-stock │ │ │ │ ├── _component │ │ │ │ │ ├── list-product.tsx │ │ │ │ │ └── product-form.tsx │ │ │ │ └── page.tsx │ │ │ │ └── finish │ │ │ │ └── page.tsx │ │ ├── logout │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ └── page.tsx │ │ └── sales │ │ │ └── page.tsx │ ├── _layout-components │ │ ├── navbar │ │ │ ├── components │ │ │ │ ├── feedback-form-modal │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── hooks.ts │ │ │ │ │ └── feedback-form-modal.tsx │ │ │ │ ├── guest-navbar-items.tsx │ │ │ │ └── auth-navbar-items │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── components │ │ │ │ │ └── settings-dropdown-button.tsx │ │ │ ├── navbar-items-no-ssr.tsx │ │ │ ├── navbar-items.tsx │ │ │ └── index.tsx │ │ ├── font.ts │ │ ├── hero.ts │ │ ├── main.css │ │ ├── page-indicator.tsx │ │ └── providers.tsx │ ├── (public) │ │ └── onboarding │ │ │ └── steps │ │ │ ├── layout.tsx │ │ │ └── 1-add-admin-user │ │ │ └── page.tsx │ ├── page.tsx │ ├── manifest.webmanifest │ ├── global-error.tsx │ ├── _page-sections │ │ ├── cta.tsx │ │ ├── faq.tsx │ │ ├── footer.tsx │ │ └── hero.tsx │ └── layout.tsx ├── enums │ ├── role.ts │ ├── security-question.ts │ ├── permission.ts │ └── page-url.ts ├── functions │ ├── generate-ordered-uuid.ts │ ├── is-uuid-string.ts │ ├── format-number.ts │ ├── get-hash.ts │ ├── merge-class.ts │ └── toast.tsx ├── hooks │ ├── use-is-app-already-initialized.ts │ ├── use-debounce.ts │ └── use-auth.ts ├── models │ ├── table-types │ │ ├── product-movement │ │ │ ├── sale-additional-info.ts │ │ │ ├── additional-cost.ts │ │ │ ├── item.ts │ │ │ └── purchase-additional-info.ts │ │ ├── user.ts │ │ ├── product.ts │ │ └── product-movement.ts │ └── db.ts ├── stores │ ├── form-submission.ts │ └── input-error-message.ts ├── instrumentation.ts ├── data │ ├── permission-templates.ts │ └── penjualan.ts ├── instrumentation-client.ts └── assets │ └── logo.svg ├── .env.example ├── public └── assets │ └── pwas │ ├── icon512_maskable.png │ └── icon512_rounded.png ├── postcss.config.mjs ├── next-configs ├── with-bundler-analyzer.ts └── with-sentry-config.ts ├── .gitignore ├── .github └── workflows │ └── code-check.yml ├── next.config.ts ├── sentry.server.config.ts ├── tsconfig.json ├── sentry.edge.config.ts ├── biome.json ├── LICENSE ├── README.md ├── package.json └── docs └── index.md /src/@types/iso-date.ts: -------------------------------------------------------------------------------- 1 | export type ISODate = string 2 | -------------------------------------------------------------------------------- /src/@types/base-64-blob.ts: -------------------------------------------------------------------------------- 1 | export type Base64Blob = string 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_APP_NAME="Sensasi POS" 2 | SENTRY_AUTH_TOKEN= 3 | -------------------------------------------------------------------------------- /src/components/image-input/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImageInput } from './image-input' 2 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/src/app/icon1.png -------------------------------------------------------------------------------- /src/app/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/src/app/icon2.png -------------------------------------------------------------------------------- /src/app/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/src/app/icon3.png -------------------------------------------------------------------------------- /src/app/icon4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/src/app/icon4.png -------------------------------------------------------------------------------- /src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/src/app/apple-icon.png -------------------------------------------------------------------------------- /src/app/(guest)/forgot-password/_components/security-question/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './security-question' 2 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/products/_components/product-card/index.ts: -------------------------------------------------------------------------------- 1 | export { ProductCard } from './product-card' 2 | -------------------------------------------------------------------------------- /src/app/(guest)/reset-password/_components/reset-password-form/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './reset-password-form' 2 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/products/(form)/_components/product-form/index.ts: -------------------------------------------------------------------------------- 1 | export { ProductForm } from './product-form' 2 | -------------------------------------------------------------------------------- /public/assets/pwas/icon512_maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/public/assets/pwas/icon512_maskable.png -------------------------------------------------------------------------------- /public/assets/pwas/icon512_rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensasi-apps/sensasi-pos/HEAD/public/assets/pwas/icon512_rounded.png -------------------------------------------------------------------------------- /src/app/_layout-components/navbar/components/feedback-form-modal/index.ts: -------------------------------------------------------------------------------- 1 | export { FeedbackFormModal } from './feedback-form-modal' 2 | -------------------------------------------------------------------------------- /src/enums/role.ts: -------------------------------------------------------------------------------- 1 | export enum Role { 2 | OWNER = 'owner', 3 | MANAGER = 'manager', 4 | CASHIER = 'cashier', 5 | STOCKER = 'stocker', 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | '@tailwindcss/postcss': {}, 5 | }, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/settings/database/export/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return ( 3 |
4 |
(Halaman Ekspor Basis Data)
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/settings/database/sync/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return ( 3 |
4 |
(Halaman Sinkronisasi Basis Data)
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/reports/sale-per-product/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return ( 3 |
4 |
(Halaman Laporan Penjualan per Produk)
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/reports/sale-per-tx/page.tsx: -------------------------------------------------------------------------------- 1 | export default function page() { 2 | return ( 3 |
4 |
(Halaman Laporan Penjualan per Transaksi)
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/_layout-components/font.ts: -------------------------------------------------------------------------------- 1 | import { Raleway } from 'next/font/google' 2 | 3 | export const sans = Raleway({ 4 | subsets: ['latin'], 5 | display: 'swap', 6 | weight: ['300', '400', '500', '700'], 7 | }) 8 | -------------------------------------------------------------------------------- /src/functions/generate-ordered-uuid.ts: -------------------------------------------------------------------------------- 1 | import type { UUID } from 'node:crypto' 2 | import { v7 as generateUuid } from 'uuid' 3 | 4 | export function generateOrderedUuid(): UUID { 5 | return generateUuid() as UUID 6 | } 7 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/reports/stock-in-out-per-product/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return ( 3 |
4 |
(Halaman Laporan Stok Masuk-Keluar per Produk)
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/reports/sale-per-product-category/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return ( 3 |
4 |
(Halaman Laporan Penjualan per Kategori Produk)
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/functions/is-uuid-string.ts: -------------------------------------------------------------------------------- 1 | import { validate } from 'uuid' 2 | 3 | /** 4 | * Checks if the provided string is a valid UUID. 5 | */ 6 | export function isUuidString(string: string): boolean { 7 | return validate(string) 8 | } 9 | -------------------------------------------------------------------------------- /src/functions/format-number.ts: -------------------------------------------------------------------------------- 1 | export default function formatNumber( 2 | num: number, 3 | locale?: Intl.Locale, 4 | options?: Intl.NumberFormatOptions, 5 | ) { 6 | return new Intl.NumberFormat(locale ?? 'id-ID', options).format(num) 7 | } 8 | -------------------------------------------------------------------------------- /next-configs/with-bundler-analyzer.ts: -------------------------------------------------------------------------------- 1 | import createWithBundleAnalyzer from '@next/bundle-analyzer' 2 | 3 | const withBundleAnalyzer = createWithBundleAnalyzer({ 4 | enabled: process.env.ANALYZE === 'true', 5 | }) 6 | 7 | export default withBundleAnalyzer 8 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/users/(form)/_types/form-values.ts: -------------------------------------------------------------------------------- 1 | import type { User } from '@/models/table-types/user' 2 | 3 | export type FormValues = Partial< 4 | Omit & { 5 | pin: string 6 | pin_confirmation: string 7 | } 8 | > 9 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/products/(form)/_components/product-form/props.ts: -------------------------------------------------------------------------------- 1 | import type { Product } from '@/models/table-types/product' 2 | 3 | export interface ProductFormProps { 4 | id: HTMLFormElement['id'] 5 | data: Partial 6 | onSubmit?: (values: Partial) => void 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/products/(form)/create/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ProductFormPageTemplate } from '../_components/page-template' 4 | import { useHook } from './_hook' 5 | 6 | export default function ProductCreatePage() { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /src/app/_layout-components/navbar/navbar-items-no-ssr.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import dynamic from 'next/dynamic' 4 | 5 | const NavbarItems = dynamic(() => import('./navbar-items'), { 6 | ssr: false, 7 | }) 8 | 9 | export default function NavbarItemsNoSSR() { 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/purchases/layout.ts: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | export const metadata: Metadata = { 4 | title: `Pembelian — ${process.env.NEXT_PUBLIC_APP_NAME}`, 5 | } 6 | 7 | export default function Layout({ children }: { children: React.ReactNode }) { 8 | return children 9 | } 10 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/purchases/(form)/_functions/validate-form-values.ts: -------------------------------------------------------------------------------- 1 | import type { ProductMovement } from '@/models/table-types/product-movement' 2 | import type { FormValues } from '../_types/form-values' 3 | 4 | export function validateFormValues(formValues: FormValues) { 5 | return formValues as ProductMovement 6 | } 7 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/purchases/(form)/_types/form-values.ts: -------------------------------------------------------------------------------- 1 | import type { ProductMovement } from '@/models/table-types/product-movement' 2 | 3 | export type FormValues = Partial & { 4 | items: Partial[] 5 | additional_costs: Partial[] 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/use-is-app-already-initialized.ts: -------------------------------------------------------------------------------- 1 | import db from '@/models/db' 2 | import { useLiveQuery } from 'dexie-react-hooks' 3 | 4 | export default function useIsAppAlreadyInitialized() { 5 | return useLiveQuery(async () => { 6 | const userCount = await db.users.count() 7 | return userCount > 0 8 | }, []) 9 | } 10 | -------------------------------------------------------------------------------- /src/app/(guest)/layout.tsx: -------------------------------------------------------------------------------- 1 | import RedirectIfAuthenticated from '@/components/redirect-if-authenticated' 2 | 3 | export default function GuestLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode 7 | }) { 8 | return ( 9 | <> 10 | 11 | {children} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/app/_layout-components/hero.ts: -------------------------------------------------------------------------------- 1 | import { heroui } from '@heroui/theme' 2 | 3 | export default heroui({ 4 | themes: { 5 | light: { 6 | colors: { 7 | background: '#f8f8f8', 8 | }, 9 | }, 10 | dark: { 11 | colors: { 12 | background: '#111111', 13 | }, 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /src/components/input-additional-content.tsx: -------------------------------------------------------------------------------- 1 | export function InputAdditionalContent({ 2 | children, 3 | }: { 4 | children?: React.ReactNode 5 | }) { 6 | return ( 7 |
8 | {children} 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/models/table-types/product-movement/sale-additional-info.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface representing additional information for a product movement sale. 3 | */ 4 | export interface ProductMovementSaleAdditionalInfo { 5 | /** 6 | * The number of the receipt associated with the sale. 7 | */ 8 | receipt_no: Readonly 9 | } 10 | -------------------------------------------------------------------------------- /src/app/(guest)/login/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import LoginForm from './login-form' 3 | 4 | export const metadata: Metadata = { 5 | title: 'Masuk — Sensasi POS', 6 | description: 'Halaman masuk untuk mengakses aplikasi Sensasi POS', 7 | } 8 | 9 | export default function Page() { 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /src/functions/get-hash.ts: -------------------------------------------------------------------------------- 1 | import { genSaltSync, hashSync } from 'bcryptjs' 2 | 3 | /** 4 | * Generates a hash for the given string using bcrypt. 5 | * 6 | * @param string - The string to be hashed. 7 | * @returns string - The hashed string. 8 | */ 9 | export function getHash(string: string) { 10 | return hashSync(string, genSaltSync(10)) 11 | } 12 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/users/(form)/create/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { UserForm } from '../_components/form' 4 | import { useHook } from './hook' 5 | 6 | export default function Page() { 7 | return ( 8 |
9 |
Tambah Data Pengguna
10 | 11 | 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/layout.tsx: -------------------------------------------------------------------------------- 1 | import RedirectIfUnauthenticated from '@/components/redirect-if-unauthenticated' 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return ( 5 | <> 6 | 7 |
{children}
8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src/functions/merge-class.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | /** 5 | * Merge multiple class names into one. 6 | * 7 | * @param classNames - The class names to merge. 8 | * @returns string 9 | */ 10 | export default function mergeClass(...classNames: ClassValue[]) { 11 | return twMerge(clsx(classNames)) 12 | } 13 | -------------------------------------------------------------------------------- /src/models/table-types/product-movement/additional-cost.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface representing an additional cost associated with a product movement. 3 | */ 4 | export interface ProductMovementAdditionalCost { 5 | /** 6 | * Name of the additional cost. 7 | */ 8 | name: string 9 | 10 | /** 11 | * Value of the additional cost. 12 | */ 13 | value: number 14 | } 15 | -------------------------------------------------------------------------------- /src/stores/form-submission.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | export interface FormSubmissionState { 4 | isSubmitting: boolean 5 | toggleSubmitting: () => void 6 | } 7 | 8 | export const useFormSubmissionState = create(set => ({ 9 | isSubmitting: false, 10 | toggleSubmitting: () => set(state => ({ isSubmitting: !state.isSubmitting })), 11 | })) 12 | -------------------------------------------------------------------------------- /src/instrumentation.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/nextjs' 2 | 3 | export async function register() { 4 | if (process.env.NEXT_RUNTIME === 'nodejs') { 5 | await import('../sentry.server.config') 6 | } 7 | 8 | if (process.env.NEXT_RUNTIME === 'edge') { 9 | await import('../sentry.edge.config') 10 | } 11 | } 12 | 13 | export const onRequestError = Sentry.captureRequestError 14 | -------------------------------------------------------------------------------- /src/functions/toast.tsx: -------------------------------------------------------------------------------- 1 | import toastVendor from 'react-hot-toast' 2 | import { Alert, type AlertProps } from '@heroui/alert' 3 | 4 | export function toast(message: React.ReactNode, color?: AlertProps['color']) { 5 | return toastVendor.custom( 6 | , 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/app/(public)/onboarding/steps/layout.tsx: -------------------------------------------------------------------------------- 1 | import Steps from '@/components/stepper' 2 | import type { ReactNode } from 'react' 3 | 4 | const Layout = ({ children }: { children: ReactNode }) => { 5 | return ( 6 | <> 7 |
8 | 9 |
10 | {children} 11 | 12 | ) 13 | } 14 | 15 | export default Layout 16 | -------------------------------------------------------------------------------- /src/app/_layout-components/main.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @plugin "./hero.ts"; 4 | 5 | @source "../../../node_modules/@heroui/theme/dist/components/*.js"; 6 | 7 | @layer components { 8 | .container { 9 | margin-right: auto; 10 | margin-left: auto; 11 | padding: 2rem; 12 | } 13 | } 14 | 15 | @custom-variant dark (&:is(.dark *)); 16 | 17 | .text-align-unset { 18 | text-align: unset; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/onboarding/steps/layout.tsx: -------------------------------------------------------------------------------- 1 | import Steps from '@/components/stepper' 2 | import type { ReactNode } from 'react' 3 | 4 | const Layout = ({ children }: { children: ReactNode }) => { 5 | return ( 6 | <> 7 |
8 | 9 |
10 | {children} 11 | 12 | ) 13 | } 14 | 15 | export default Layout 16 | -------------------------------------------------------------------------------- /src/app/_layout-components/navbar/navbar-items.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import useAuth from '@/hooks/use-auth' 4 | import { GuestNavbarItems } from './components/guest-navbar-items' 5 | import { AuthNavbarItems } from './components/auth-navbar-items' 6 | 7 | export default function NavbarItems() { 8 | const { user } = useAuth() 9 | 10 | if (!user) { 11 | return 12 | } 13 | 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/products/(form)/[uuid]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ProductFormPageTemplate } from '../_components/page-template' 4 | import { useHook } from './_hook' 5 | import { use } from 'react' 6 | 7 | export default function ProductUpdatePage({ 8 | params, 9 | }: { 10 | params: Promise<{ uuid: string }> 11 | }) { 12 | const { uuid } = use(params) 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /src/components/progress-bar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { AppProgressBar } from 'next-nprogress-bar' 4 | import type { ReactNode } from 'react' 5 | 6 | export default function ProgressBar({ children }: { children: ReactNode }) { 7 | return ( 8 | <> 9 | {children} 10 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/use-debounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export function useDebounce(value: T, delay: number = 500): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value) 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value) 9 | }, delay) 10 | 11 | return () => { 12 | clearTimeout(handler) 13 | } 14 | }, [value, delay]) 15 | 16 | return debouncedValue 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/data/products/_hook.ts: -------------------------------------------------------------------------------- 1 | import db from '@/models/db' 2 | import { useLiveQuery } from 'dexie-react-hooks' 3 | 4 | export function useHook() { 5 | const products = useLiveQuery(() => db.products.toArray()) 6 | const nProducts = products?.length ?? 0 7 | const categories = 8 | products 9 | ?.map(product => product.category) 10 | .filter((v, i, a) => v && a.indexOf(v) === i) ?? [] 11 | 12 | return { products, nProducts, categories } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/(authenticated user)/logout/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import useAuth from '@/hooks/use-auth' 4 | import { Spinner } from '@heroui/spinner' 5 | import { useEffect, useEffectEvent } from 'react' 6 | 7 | export default function Page() { 8 | const { setLoggedInUser } = useAuth() 9 | 10 | const loggingOut = useEffectEvent(() => { 11 | setLoggedInUser(undefined) 12 | }) 13 | 14 | useEffect(() => { 15 | loggingOut() 16 | }, []) 17 | 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Footer } from './_page-sections/footer' 2 | import { Hero } from './_page-sections/hero' 3 | import { Faq } from './_page-sections/faq' 4 | import { Cta } from './_page-sections/cta' 5 | import RedirectIfAuthenticated from '@/components/redirect-if-authenticated' 6 | 7 | export default function Home() { 8 | return ( 9 | <> 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |