├── .eslintrc.json ├── public ├── favicon.ico ├── assets │ └── img │ │ ├── mailbox.jpg │ │ ├── mailbox-dark.jpg │ │ ├── login-office.jpeg │ │ ├── login-office-dark.jpeg │ │ ├── create-account-office.jpeg │ │ ├── forgot-password-office.jpeg │ │ ├── create-account-office-dark.jpeg │ │ └── forgot-password-office-dark.jpeg └── vercel.svg ├── .github └── windmill-dashboard-thumbnail.jpg ├── icons ├── moon.svg ├── edit.svg ├── bell.svg ├── tables.svg ├── chat.svg ├── dropdown.svg ├── forbidden.svg ├── outlinePerson.svg ├── search.svg ├── menu.svg ├── money.svg ├── mail.svg ├── outlineLogout.svg ├── heart.svg ├── charts.svg ├── modals.svg ├── people.svg ├── buttons.svg ├── cards.svg ├── cart.svg ├── home.svg ├── forms.svg ├── trash.svg ├── pages.svg ├── star.svg ├── sun.svg ├── twitter.svg ├── outlineCog.svg ├── github.svg └── index.ts ├── .vscode └── settings.json ├── postcss.config.js ├── next-env.d.ts ├── pages ├── example │ ├── blank.tsx │ ├── 404.tsx │ ├── charts.tsx │ ├── modals.tsx │ ├── forgot-password.tsx │ ├── password-reset │ │ └── [token].tsx │ ├── verify-email.tsx │ ├── cards.tsx │ ├── buttons.tsx │ ├── create-account.tsx │ ├── login.tsx │ ├── index.tsx │ ├── tables.tsx │ └── forms.tsx ├── api │ └── hello.ts ├── _app.tsx └── index.tsx ├── example ├── components │ ├── ThemedSuspense.tsx │ ├── Sidebar │ │ ├── index.tsx │ │ ├── DesktopSidebar.tsx │ │ ├── MobileSidebar.tsx │ │ ├── SidebarContent.tsx │ │ └── SidebarSubmenu.tsx │ ├── Typography │ │ ├── SectionTitle.tsx │ │ └── PageTitle.tsx │ ├── Chart │ │ ├── ChartCard.tsx │ │ └── ChartLegend.tsx │ ├── RoundIcon.tsx │ ├── Cards │ │ └── InfoCard.tsx │ ├── AccessibleNavigationAnnouncer.tsx │ ├── CTA.tsx │ ├── Loader │ │ ├── Loader.tsx │ │ └── Loader.module.css │ └── Header.tsx └── containers │ ├── Main.tsx │ └── Layout.tsx ├── styles ├── globals.css └── Home.module.css ├── tailwind.config.js ├── lib └── api.ts ├── next.config.js ├── .gitignore ├── .env.example ├── tsconfig.json ├── package.json ├── LICENSE ├── routes └── sidebar.tsx ├── context ├── ThemeContext.tsx └── SidebarContext.tsx ├── utils └── demo │ ├── chartsData.ts │ └── tableData.ts ├── hooks └── auth.ts └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/assets/img/mailbox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/mailbox.jpg -------------------------------------------------------------------------------- /public/assets/img/mailbox-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/mailbox-dark.jpg -------------------------------------------------------------------------------- /public/assets/img/login-office.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/login-office.jpeg -------------------------------------------------------------------------------- /.github/windmill-dashboard-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/.github/windmill-dashboard-thumbnail.jpg -------------------------------------------------------------------------------- /public/assets/img/login-office-dark.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/login-office-dark.jpeg -------------------------------------------------------------------------------- /icons/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/assets/img/create-account-office.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/create-account-office.jpeg -------------------------------------------------------------------------------- /public/assets/img/forgot-password-office.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/forgot-password-office.jpeg -------------------------------------------------------------------------------- /public/assets/img/create-account-office-dark.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/create-account-office-dark.jpeg -------------------------------------------------------------------------------- /public/assets/img/forgot-password-office-dark.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roketid/windmill-nextjs-laravel-breeze/HEAD/public/assets/img/forgot-password-office-dark.jpeg -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.formatOnPaste": true, 5 | "editor.tabSize": 2 6 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | cssnano: { 6 | preset: 'default', 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /icons/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /icons/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /icons/tables.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /icons/chat.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /icons/dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /icons/forbidden.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /icons/outlinePerson.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icons/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /icons/money.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /pages/example/blank.tsx: -------------------------------------------------------------------------------- 1 | import PageTitle from 'example/components/Typography/PageTitle' 2 | import Layout from 'example/containers/Layout' 3 | 4 | function Blank() { 5 | return ( 6 | 7 | Blank 8 | 9 | ) 10 | } 11 | 12 | export default Blank 13 | -------------------------------------------------------------------------------- /icons/mail.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /icons/outlineLogout.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /icons/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/components/ThemedSuspense.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function ThemedSuspense() { 4 | return ( 5 |
6 | Loading... 7 |
8 | ) 9 | } 10 | 11 | export default ThemedSuspense 12 | -------------------------------------------------------------------------------- /icons/charts.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /icons/modals.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /icons/people.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/components/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DesktopSidebar from './DesktopSidebar' 3 | import MobileSidebar from './MobileSidebar' 4 | 5 | function Sidebar() { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default Sidebar 15 | -------------------------------------------------------------------------------- /icons/buttons.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /example/components/Typography/SectionTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface ISectionTitle{ 4 | children: React.ReactNode 5 | } 6 | 7 | function SectionTitle({ children }: ISectionTitle) { 8 | return

{children}

9 | } 10 | 11 | export default SectionTitle 12 | -------------------------------------------------------------------------------- /example/components/Typography/PageTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface IPageTitle{ 4 | children: React.ReactNode 5 | } 6 | 7 | function PageTitle({ children }: IPageTitle) { 8 | return ( 9 |

{children}

10 | ) 11 | } 12 | 13 | export default PageTitle 14 | -------------------------------------------------------------------------------- /icons/cards.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /icons/cart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /icons/home.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | const windmill = require('@roketid/windmill-react-ui/config') 3 | module.exports = windmill({ 4 | content: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', './containers/**/*.{js,ts,jsx,tsx}', './example/**/*.{js,ts,jsx,tsx}'], 5 | extend: {}, 6 | plugins: [], 7 | }) 8 | -------------------------------------------------------------------------------- /example/containers/Main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface IMain{ 4 | children: React.ReactNode 5 | } 6 | 7 | function Main({ children }: IMain) { 8 | return ( 9 |
10 |
{children}
11 |
12 | ); 13 | } 14 | 15 | export default Main 16 | -------------------------------------------------------------------------------- /icons/forms.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /icons/trash.svg: -------------------------------------------------------------------------------- 1 | 5 | 10 | -------------------------------------------------------------------------------- /lib/api.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const baseURL = process.env.NEXT_PUBLIC_BACKEND_URL 4 | 5 | const instance = axios.create({ 6 | withCredentials: true, 7 | headers: { 8 | 'X-Requested-With': 'XMLHttpRequest', 9 | }, 10 | baseURL: baseURL 11 | }) 12 | 13 | export const csrf = () => instance.get(baseURL + '/sanctum/csrf-cookie') 14 | 15 | export default instance 16 | -------------------------------------------------------------------------------- /icons/pages.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/components/Chart/ChartCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface IChart{ 4 | children: React.ReactNode 5 | title: string 6 | } 7 | 8 | function Chart({ children, title }: IChart) { 9 | return ( 10 |
11 |

{title}

12 | {children} 13 |
14 | ) 15 | } 16 | 17 | export default Chart 18 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: false, 4 | webpack(config) { 5 | config.module.rules.push({ 6 | test: /\.svg$/i, 7 | issuer: /\.[jt]sx?$/, 8 | use: ['@svgr/webpack'], 9 | }) 10 | 11 | return config 12 | }, 13 | async redirects() { 14 | return [ 15 | { 16 | source: '/', 17 | destination: '/example/login', 18 | permanent: false, 19 | }, 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import 'tailwindcss/tailwind.css'; 3 | 4 | import React from 'react' 5 | import { Windmill } from '@roketid/windmill-react-ui' 6 | import type { AppProps } from 'next/app' 7 | 8 | function MyApp({ Component, pageProps }: AppProps) { 9 | // suppress useLayoutEffect warnings when running outside a browser 10 | if (!process.browser) React.useLayoutEffect = React.useEffect; 11 | 12 | return ( 13 | 14 | 15 | 16 | ) 17 | } 18 | export default MyApp 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 2 | 3 | # NEXT_PUBLIC_API_PATH_USER='/api/user' 4 | # NEXT_PUBLIC_API_PATH_LOGIN='/login' 5 | # NEXT_PUBLIC_API_PATH_REGISTER='/register' 6 | # NEXT_PUBLIC_API_PATH_FORGOT_PASSWORD='/forgot-password' 7 | # NEXT_PUBLIC_API_PATH_RESET_PASSWORD='/reset-password' 8 | # NEXT_PUBLIC_API_PATH_VERIFY_EMAIL='/email/verification-notification' 9 | # NEXT_PUBLIC_API_PATH_LOGIN='/logout' 10 | 11 | # NEXT_PUBLIC_AUTH_PAGE_LOGIN='/example/login' 12 | # NEXT_PUBLIC_AUTH_PAGE_VERIFY_EMAIL='/example/verify-email' 13 | # NEXT_PUBLIC_AUTH_REDIRECTED='/example' 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "baseUrl": ".", 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /icons/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /icons/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/components/Chart/ChartLegend.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import {ILegends} from 'utils/demo/chartsData' 4 | 5 | interface IChartLegend{ 6 | legends: ILegends[] 7 | } 8 | 9 | function ChartLegend({ legends }: IChartLegend) { 10 | return ( 11 |
12 | {legends.map((legend) => ( 13 |
14 | 15 | {legend.title} 16 |
17 | ))} 18 |
19 | ) 20 | } 21 | 22 | export default ChartLegend 23 | -------------------------------------------------------------------------------- /example/components/RoundIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | 4 | interface IRoundIcon{ 5 | icon: any 6 | className: string 7 | iconColorClass: string 8 | bgColorClass: string 9 | } 10 | 11 | function RoundIcon({ 12 | icon, 13 | iconColorClass = 'text-purple-600 dark:text-purple-100', 14 | bgColorClass = 'bg-purple-100 dark:bg-purple-600', 15 | className 16 | }: IRoundIcon) { 17 | const baseStyle = 'p-3 rounded-full' 18 | const cls = classNames(baseStyle, iconColorClass, bgColorClass, className) 19 | return( 20 |
21 | {/* */} 22 |
23 | ) 24 | } 25 | 26 | export default RoundIcon 27 | -------------------------------------------------------------------------------- /example/components/Cards/InfoCard.tsx: -------------------------------------------------------------------------------- 1 | import { ReactSVGElement } from 'react' 2 | import { Card, CardBody } from '@roketid/windmill-react-ui' 3 | 4 | interface IInfoCard{ 5 | title: string 6 | value: string 7 | children?: ReactSVGElement 8 | } 9 | 10 | function InfoCard({ title, value, children }: IInfoCard) { 11 | return ( 12 | 13 | 14 | {children} 15 |
16 |

{title}

17 |

{value}

18 |
19 |
20 |
21 | ) 22 | } 23 | 24 | export default InfoCard 25 | -------------------------------------------------------------------------------- /icons/outlineCog.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/components/Sidebar/DesktopSidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useRef } from 'react' 2 | import SidebarContext from 'context/SidebarContext' 3 | import SidebarContent from './SidebarContent' 4 | 5 | function DesktopSidebar() { 6 | const sidebarRef = useRef(null) 7 | const { saveScroll } = useContext(SidebarContext) 8 | 9 | const linkClickedHandler = () => { 10 | saveScroll(sidebarRef.current) 11 | } 12 | 13 | return ( 14 | 21 | ) 22 | } 23 | 24 | export default DesktopSidebar 25 | -------------------------------------------------------------------------------- /icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/example/404.tsx: -------------------------------------------------------------------------------- 1 | import Layout from 'example/containers/Layout' 2 | import { ForbiddenIcon } from 'icons' 3 | 4 | function Page404() { 5 | return ( 6 | 7 |
8 |
18 |
19 | ) 20 | } 21 | 22 | export default Page404 23 | -------------------------------------------------------------------------------- /example/components/AccessibleNavigationAnnouncer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useRouter } from 'next/router' 3 | 4 | function AccessibleNavigationAnnouncer() { 5 | const [message, setMessage] = useState('') 6 | const router = useRouter() 7 | 8 | useEffect(() => { 9 | // ignore the / 10 | if (router.pathname.slice(1)) { 11 | // make sure navigation has occurred and screen reader is ready 12 | setTimeout(() => setMessage(`Navigated to ${router.pathname.slice(1)} page.`), 500) 13 | } else { 14 | setMessage('') 15 | } 16 | }, [router]) 17 | 18 | return ( 19 | 20 | {message} 21 | 22 | ) 23 | } 24 | 25 | export default AccessibleNavigationAnnouncer 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "windmill-dashboard-nextjs-typescript", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.26.0", 13 | "chart.js": "^3.6.1", 14 | "classnames": "^2.3.1", 15 | "next": "11.1.2", 16 | "react": "17.0.2", 17 | "react-chartjs-2": "^4.0.0", 18 | "react-dom": "17.0.2", 19 | "swr": "^1.2.1" 20 | }, 21 | "devDependencies": { 22 | "@roketid/windmill-react-ui": "^0.1.1", 23 | "@svgr/webpack": "^6.1.0", 24 | "@types/react": "17.0.21", 25 | "autoprefixer": "^10.4.2", 26 | "cssnano": "^5.0.12", 27 | "eslint": "7.32.0", 28 | "eslint-config-next": "11.1.2", 29 | "postcss": "^8.4.6", 30 | "tailwindcss": "^3.0.22", 31 | "typescript": "4.4.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/components/CTA.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface ICTA { 4 | message: string 5 | url?: string 6 | icon?: any 7 | text?: string 8 | } 9 | 10 | function CTA({url, icon, message, text}: ICTA) { 11 | const Icon = icon 12 | return ( 13 | 17 |
18 | {icon 19 | ? 20 | : null 21 | } 22 | {message} 23 |
24 | { 25 | url 26 | ? 27 | {text || `View more`} 28 | 29 | : null 30 | } 31 |
32 | ) 33 | } 34 | 35 | export default CTA 36 | -------------------------------------------------------------------------------- /example/components/Loader/Loader.tsx: -------------------------------------------------------------------------------- 1 | import styles from './Loader.module.css' 2 | 3 | function Loader() { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Please wait... 16 |
17 |
18 |
19 |
20 | ) 21 | } 22 | 23 | export default Loader 24 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/components/Loader/Loader.module.css: -------------------------------------------------------------------------------- 1 | .LoaderDots div { 2 | animation-timing-function: cubic-bezier(0, 1, 1, 0); 3 | } 4 | .LoaderDots div:nth-child(1) { 5 | left: 8px; 6 | animation: LoaderDots1 0.6s infinite; 7 | } 8 | .LoaderDots div:nth-child(2) { 9 | left: 8px; 10 | animation: LoaderDots2 0.6s infinite; 11 | } 12 | .LoaderDots div:nth-child(3) { 13 | left: 32px; 14 | animation: LoaderDots2 0.6s infinite; 15 | } 16 | .LoaderDots div:nth-child(4) { 17 | left: 56px; 18 | animation: LoaderDots3 0.6s infinite; 19 | } 20 | @keyframes LoaderDots1 { 21 | 0% { 22 | transform: scale(0); 23 | } 24 | 100% { 25 | transform: scale(1); 26 | } 27 | } 28 | @keyframes LoaderDots3 { 29 | 0% { 30 | transform: scale(1); 31 | } 32 | 100% { 33 | transform: scale(0); 34 | } 35 | } 36 | @keyframes LoaderDots2 { 37 | 0% { 38 | transform: translate(0, 0); 39 | } 40 | 100% { 41 | transform: translate(24px, 0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Estevan Maito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/containers/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react' 2 | import SidebarContext, { SidebarProvider } from 'context/SidebarContext' 3 | import Sidebar from 'example/components/Sidebar' 4 | import Header from 'example/components/Header' 5 | import Main from './Main' 6 | import { useAuth } from 'hooks/auth' 7 | import Loader from 'example/components/Loader/Loader' 8 | 9 | interface ILayout{ 10 | children: React.ReactNode 11 | } 12 | 13 | function Layout({ children }: ILayout) { 14 | const [showLoader, setShowLoaderState] = useState(true) 15 | 16 | const { user, loading, logout } = useAuth({ middleware: 'auth' }) 17 | const { isSidebarOpen } = useContext(SidebarContext) 18 | 19 | useEffect(() => { 20 | if (!loading && user) { 21 | setShowLoaderState(false) 22 | } 23 | 24 | return () => { 25 | setShowLoaderState(true) 26 | } 27 | }, [loading, user]) 28 | 29 | return showLoader 30 | ? 31 | : 32 |
35 | 36 |
37 |
38 |
39 | {children} 40 |
41 |
42 |
43 |
44 | } 45 | 46 | export default Layout -------------------------------------------------------------------------------- /pages/example/charts.tsx: -------------------------------------------------------------------------------- 1 | import { Doughnut, Line, Bar } from 'react-chartjs-2' 2 | import ChartCard from 'example/components/Chart/ChartCard' 3 | import ChartLegend from 'example/components/Chart/ChartLegend' 4 | import PageTitle from 'example/components/Typography/PageTitle' 5 | import Layout from 'example/containers/Layout' 6 | import { 7 | doughnutOptions, 8 | lineOptions, 9 | barOptions, 10 | doughnutLegends, 11 | lineLegends, 12 | barLegends, 13 | } from 'utils/demo/chartsData' 14 | import { 15 | Chart, 16 | ArcElement, 17 | BarElement, 18 | CategoryScale, 19 | LinearScale, 20 | PointElement, 21 | LineElement, 22 | Title, 23 | Tooltip, 24 | Legend, 25 | } from 'chart.js' 26 | 27 | function Charts() { 28 | Chart.register( 29 | ArcElement, 30 | BarElement, 31 | CategoryScale, 32 | LinearScale, 33 | PointElement, 34 | LineElement, 35 | Title, 36 | Tooltip, 37 | Legend 38 | ) 39 | 40 | return ( 41 | 42 | Charts 43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 |
61 | ) 62 | } 63 | 64 | export default Charts 65 | -------------------------------------------------------------------------------- /example/components/Sidebar/MobileSidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useRef } from 'react' 2 | import { Transition, Backdrop } from '@roketid/windmill-react-ui' 3 | import SidebarContext from 'context/SidebarContext' 4 | import SidebarContent from './SidebarContent' 5 | 6 | 7 | function MobileSidebar() { 8 | const sidebarRef = useRef(null) 9 | const { isSidebarOpen, closeSidebar, saveScroll } = useContext(SidebarContext) 10 | 11 | const linkClickedHandler = () => { 12 | saveScroll(sidebarRef.current) 13 | } 14 | 15 | return ( 16 | 17 | <> 18 | 26 | 27 | 28 | 29 | 37 | 44 | 45 | 46 | 47 | ) 48 | } 49 | 50 | export default MobileSidebar 51 | -------------------------------------------------------------------------------- /icons/index.ts: -------------------------------------------------------------------------------- 1 | import ButtonsIcon from './buttons.svg' 2 | import CardsIcon from './cards.svg' 3 | import ChartsIcon from './charts.svg' 4 | import FormsIcon from './forms.svg' 5 | import HomeIcon from './home.svg' 6 | import ModalsIcon from './modals.svg' 7 | import PagesIcon from './pages.svg' 8 | import TablesIcon from './tables.svg' 9 | import HeartIcon from './heart.svg' 10 | import EditIcon from './edit.svg' 11 | import TrashIcon from './trash.svg' 12 | import ForbiddenIcon from './forbidden.svg' 13 | import GithubIcon from './github.svg' 14 | import TwitterIcon from './twitter.svg' 15 | import MailIcon from './mail.svg' 16 | import CartIcon from './cart.svg' 17 | import ChatIcon from './chat.svg' 18 | import MoneyIcon from './money.svg' 19 | import PeopleIcon from './people.svg' 20 | import SearchIcon from './search.svg' 21 | import MoonIcon from './moon.svg' 22 | import SunIcon from './sun.svg' 23 | import StarIcon from './star.svg' 24 | import BellIcon from './bell.svg' 25 | import MenuIcon from './menu.svg' 26 | import DropdownIcon from './dropdown.svg' 27 | import OutlinePersonIcon from './outlinePerson.svg' 28 | import OutlineCogIcon from './outlineCog.svg' 29 | import OutlineLogoutIcon from './outlineLogout.svg' 30 | 31 | export { 32 | ButtonsIcon, 33 | CardsIcon, 34 | ChartsIcon, 35 | FormsIcon, 36 | HomeIcon, 37 | ModalsIcon, 38 | PagesIcon, 39 | TablesIcon, 40 | HeartIcon, 41 | EditIcon, 42 | TrashIcon, 43 | ForbiddenIcon, 44 | GithubIcon, 45 | TwitterIcon, 46 | MailIcon, 47 | CartIcon, 48 | ChatIcon, 49 | MoneyIcon, 50 | PeopleIcon, 51 | SearchIcon, 52 | MoonIcon, 53 | SunIcon, 54 | StarIcon, 55 | BellIcon, 56 | MenuIcon, 57 | DropdownIcon, 58 | OutlinePersonIcon, 59 | OutlineCogIcon, 60 | OutlineLogoutIcon, 61 | } 62 | 63 | interface IIcon{ 64 | icon: string 65 | [key: string]: string | undefined 66 | } 67 | 68 | export type { 69 | IIcon 70 | }; -------------------------------------------------------------------------------- /routes/sidebar.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * ⚠ These are used just to render the Sidebar! 3 | * You can include any link here, local or external. 4 | * 5 | */ 6 | 7 | interface IRoute{ 8 | path?: string 9 | icon?: string 10 | name: string 11 | routes?: IRoute[] 12 | checkActive?(pathname: String, route: IRoute): boolean 13 | exact?: boolean 14 | } 15 | 16 | export function routeIsActive (pathname: String, route: IRoute): boolean { 17 | if (route.checkActive) { 18 | return route.checkActive(pathname, route) 19 | } 20 | 21 | return route?.exact 22 | ? pathname == route?.path 23 | : (route?.path ? pathname.indexOf(route.path) === 0 : false) 24 | } 25 | 26 | const routes: IRoute[] = [ 27 | { 28 | path: '/example', // the url 29 | icon: 'HomeIcon', // the component being exported from icons/index.js 30 | name: 'Dashboard', // name that appear in Sidebar 31 | exact: true, 32 | }, 33 | { 34 | path: '/example/forms', 35 | icon: 'FormsIcon', 36 | name: 'Forms', 37 | }, 38 | { 39 | path: '/example/cards', 40 | icon: 'CardsIcon', 41 | name: 'Cards', 42 | }, 43 | { 44 | path: '/example/charts', 45 | icon: 'ChartsIcon', 46 | name: 'Charts', 47 | }, 48 | { 49 | path: '/example/buttons', 50 | icon: 'ButtonsIcon', 51 | name: 'Buttons', 52 | }, 53 | { 54 | path: '/example/modals', 55 | icon: 'ModalsIcon', 56 | name: 'Modals', 57 | }, 58 | { 59 | path: '/example/tables', 60 | icon: 'TablesIcon', 61 | name: 'Tables', 62 | }, 63 | { 64 | icon: 'PagesIcon', 65 | name: 'Pages', 66 | routes: [ 67 | // submenu 68 | { 69 | path: '/example/login', 70 | name: 'Login', 71 | }, 72 | { 73 | path: '/example/create-account', 74 | name: 'Create account', 75 | }, 76 | { 77 | path: '/example/forgot-password', 78 | name: 'Forgot password', 79 | }, 80 | { 81 | path: '/example/404', 82 | name: '404', 83 | }, 84 | { 85 | path: '/example/blank', 86 | name: 'Blank', 87 | }, 88 | ], 89 | }, 90 | ] 91 | 92 | export type {IRoute} 93 | export default routes 94 | -------------------------------------------------------------------------------- /pages/example/modals.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from '@roketid/windmill-react-ui' 4 | import PageTitle from 'example/components/Typography/PageTitle' 5 | import CTA from 'example/components/CTA' 6 | import Layout from 'example/containers/Layout' 7 | import { StarIcon } from 'icons' 8 | 9 | function Modals() { 10 | const [isModalOpen, setIsModalOpen] = useState(false) 11 | 12 | function openModal() { 13 | setIsModalOpen(true) 14 | } 15 | 16 | function closeModal() { 17 | setIsModalOpen(false) 18 | } 19 | 20 | return ( 21 | 22 | Modals 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | Modal header 31 | 32 | Lorem, ipsum dolor sit amet consectetur adipisicing elit. Nostrum et eligendi repudiandae 33 | voluptatem tempore! 34 | 35 | 36 | {/* I don't like this approach. Consider passing a prop to ModalFooter 37 | * that if present, would duplicate the buttons in a way similar to this. 38 | * Or, maybe find some way to pass something like size="large md:regular" 39 | * to Button 40 | */} 41 |
42 | 45 |
46 |
47 | 48 |
49 |
50 | 53 |
54 |
55 | 58 |
59 |
60 |
61 |
62 | ) 63 | } 64 | 65 | export default Modals 66 | -------------------------------------------------------------------------------- /context/ThemeContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useLayoutEffect } from 'react' 2 | 3 | /** 4 | * Saves the old theme for future use 5 | * @param {string} theme - Name of curent theme 6 | * @return {string} previousTheme 7 | */ 8 | function usePrevious(theme: string) { 9 | const ref = useRef() 10 | useEffect(() => { 11 | ref.current = theme 12 | }) 13 | return ref.current 14 | } 15 | 16 | /** 17 | * Gets user preferences from local storage 18 | * @param {string} key - localStorage key 19 | * @return {array} getter and setter for user preferred theme 20 | */ 21 | function useStorageTheme(key: string): [string, React.Dispatch>]{ 22 | const userPreference = 23 | !!window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches 24 | 25 | const [theme, setTheme] = useState( 26 | // use stored theme fallback to user preference 27 | localStorage.getItem(key) || userPreference 28 | ) 29 | 30 | // update stored theme 31 | useEffect(() => { 32 | localStorage.setItem(key.toString(), theme.toString()) 33 | }, [theme, key]) 34 | 35 | return [theme.toString(), setTheme] 36 | } 37 | 38 | interface IThemeContext{ 39 | theme: string | React.Dispatch> 40 | toggleTheme: () => void 41 | } 42 | 43 | // create context 44 | export const ThemeContext = React.createContext({ theme: "", toggleTheme: () => {} }) 45 | 46 | interface IThemeProvider{ 47 | children: React.ReactNode 48 | } 49 | 50 | // create context provider 51 | export const ThemeProvider = ({ children }: IThemeProvider) => { 52 | const [theme, setTheme] = useStorageTheme('theme') 53 | 54 | // update root element class on theme change 55 | const oldTheme = usePrevious(theme.toString()) 56 | useLayoutEffect(() => { 57 | document.documentElement.classList.remove(`theme-${oldTheme}`) 58 | document.documentElement.classList.add(`theme-${theme}`) 59 | }, [theme, oldTheme]) 60 | 61 | function toggleTheme() { 62 | 63 | if (theme === 'light'){ 64 | setTheme('dark') 65 | } 66 | else{ 67 | setTheme('light') 68 | } 69 | } 70 | 71 | const context = { 72 | theme, 73 | toggleTheme, 74 | } 75 | 76 | return {children} 77 | } -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .footer { 21 | width: 100%; 22 | height: 100px; 23 | border-top: 1px solid #eaeaea; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | 29 | .footer a { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-grow: 1; 34 | } 35 | 36 | .title a { 37 | color: #0070f3; 38 | text-decoration: none; 39 | } 40 | 41 | .title a:hover, 42 | .title a:focus, 43 | .title a:active { 44 | text-decoration: underline; 45 | } 46 | 47 | .title { 48 | margin: 0; 49 | line-height: 1.15; 50 | font-size: 4rem; 51 | } 52 | 53 | .title, 54 | .description { 55 | text-align: center; 56 | } 57 | 58 | .description { 59 | line-height: 1.5; 60 | font-size: 1.5rem; 61 | } 62 | 63 | .code { 64 | background: #fafafa; 65 | border-radius: 5px; 66 | padding: 0.75rem; 67 | font-size: 1.1rem; 68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 69 | Bitstream Vera Sans Mono, Courier New, monospace; 70 | } 71 | 72 | .grid { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-wrap: wrap; 77 | max-width: 800px; 78 | margin-top: 3rem; 79 | } 80 | 81 | .card { 82 | margin: 1rem; 83 | padding: 1.5rem; 84 | text-align: left; 85 | color: inherit; 86 | text-decoration: none; 87 | border: 1px solid #eaeaea; 88 | border-radius: 10px; 89 | transition: color 0.15s ease, border-color 0.15s ease; 90 | width: 45%; 91 | } 92 | 93 | .card:hover, 94 | .card:focus, 95 | .card:active { 96 | color: #0070f3; 97 | border-color: #0070f3; 98 | } 99 | 100 | .card h2 { 101 | margin: 0 0 1rem 0; 102 | font-size: 1.5rem; 103 | } 104 | 105 | .card p { 106 | margin: 0; 107 | font-size: 1.25rem; 108 | line-height: 1.5; 109 | } 110 | 111 | .logo { 112 | height: 1em; 113 | margin-left: 0.5rem; 114 | } 115 | 116 | @media (max-width: 600px) { 117 | .grid { 118 | width: 100%; 119 | flex-direction: column; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /context/SidebarContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react' 2 | 3 | // create context 4 | 5 | interface IScrollY { 6 | id: string | null 7 | position: number 8 | } 9 | interface ISidebarContext { 10 | isSidebarOpen: boolean 11 | scrollY: IScrollY 12 | closeSidebar: () => void 13 | toggleSidebar: () => void 14 | saveScroll: (el: HTMLElement | null) => void 15 | } 16 | 17 | const SidebarContext = React.createContext( 18 | { 19 | isSidebarOpen: false, 20 | scrollY: {id: null, position: 0}, 21 | closeSidebar: () => {}, 22 | toggleSidebar: () => {}, 23 | saveScroll: (el: HTMLElement | null) => {} 24 | } 25 | ); 26 | 27 | interface ISidebarPovider{ children: React.ReactNode } 28 | 29 | export const SidebarProvider = ({ children }: ISidebarPovider) => { 30 | const [isSidebarOpen, setIsSidebarOpen] = useState(false) 31 | 32 | function toggleSidebar() { 33 | setIsSidebarOpen(!isSidebarOpen) 34 | } 35 | 36 | function closeSidebar() { 37 | setIsSidebarOpen(false) 38 | } 39 | 40 | const defaultScrollY = useMemo(() => { 41 | return {id: null, position: 0} 42 | }, []) 43 | 44 | const storageScrollY = useCallback(() => { 45 | return JSON.parse(localStorage.getItem('sidebarScrollY') || JSON.stringify(defaultScrollY)) 46 | }, [defaultScrollY]) 47 | 48 | const [scrollY, setScrollY] = useState( 49 | process.browser ? storageScrollY() : defaultScrollY 50 | ) 51 | 52 | function saveScroll(el: HTMLElement | null) { 53 | const id = el?.id || null 54 | const position = el?.scrollTop || 0 55 | setScrollY({id, position}) 56 | } 57 | 58 | useEffect(() => { 59 | if (process.browser) { 60 | localStorage.setItem('sidebarScrollY', JSON.stringify(scrollY)) 61 | } 62 | }, [scrollY]) 63 | 64 | useLayoutEffect(() => { 65 | if (process.browser) { 66 | const { id, position } = storageScrollY() 67 | document.getElementById(id)?.scrollTo(0, position) 68 | 69 | if (isSidebarOpen) { 70 | document.getElementById(id)?.scrollTo(0, position) 71 | } 72 | } 73 | }, [scrollY, storageScrollY, isSidebarOpen]) 74 | 75 | const context = { 76 | isSidebarOpen, 77 | scrollY, 78 | toggleSidebar, 79 | closeSidebar, 80 | saveScroll, 81 | } 82 | 83 | return {children} 84 | } 85 | 86 | export default SidebarContext 87 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import Image from 'next/image' 4 | import styles from '../styles/Home.module.css' 5 | 6 | const Home: NextPage = () => { 7 | return ( 8 | 69 | ) 70 | } 71 | 72 | export default Home 73 | -------------------------------------------------------------------------------- /example/components/Sidebar/SidebarContent.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import routes, { routeIsActive } from 'routes/sidebar' 3 | import * as Icons from 'icons' 4 | import { IIcon } from 'icons' 5 | import SidebarSubmenu from './SidebarSubmenu' 6 | import { Button } from '@roketid/windmill-react-ui' 7 | import { useRouter } from 'next/router' 8 | 9 | function Icon({ icon, ...props }: IIcon){ 10 | // @ts-ignore 11 | const Icon= Icons[icon] 12 | return 13 | } 14 | 15 | interface ISidebarContent{ 16 | linkClicked: () => void 17 | } 18 | 19 | function SidebarContent({ linkClicked }: ISidebarContent) { 20 | const { pathname } = useRouter(); 21 | const appName = process.env.NEXT_PUBLIC_APP_NAME 22 | 23 | return ( 24 |
25 | 26 | 33 | 34 | 71 |
72 | 78 |
79 |
80 | ) 81 | } 82 | 83 | export default SidebarContent -------------------------------------------------------------------------------- /pages/example/forgot-password.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from 'react' 2 | import Image from 'next/image' 3 | 4 | import { Label, Input, Button, WindmillContext } from '@roketid/windmill-react-ui' 5 | import { useAuth } from 'hooks/auth' 6 | 7 | interface IEvent { 8 | preventDefault: () => void 9 | } 10 | 11 | function ForgotPassword() { 12 | const { forgotPassword } = useAuth({ middleware: 'guest' }) 13 | 14 | const [submitting, setSubmittingState] = useState(false) 15 | const [email, setEmail] = useState('') 16 | const [errors, setErrors] = useState([]) 17 | const [status, setStatus] = useState(null) 18 | 19 | const submitForm = async (event: IEvent) => { 20 | event.preventDefault() 21 | 22 | setSubmittingState(true) 23 | forgotPassword({ email, setErrors, setStatus }) 24 | setSubmittingState(false) 25 | } 26 | 27 | const { mode } = useContext(WindmillContext) 28 | const imgSource = mode === 'dark' ? '/assets/img/forgot-password-office-dark.jpeg' : '/assets/img/forgot-password-office.jpeg' 29 | 30 | return ( 31 |
32 |
33 |
34 |
35 | 42 |
43 |
44 |
45 |

46 | Forgot password 47 |

48 | { 49 | errors.length && !submitting 50 | ?
{errors[0]}
51 | : `` 52 | } 53 | {status && ( 54 |
55 | {status} 56 |
57 | )} 58 | 59 | 63 | 64 | 67 |
68 |
69 |
70 |
71 |
72 | ) 73 | } 74 | 75 | export default ForgotPassword 76 | -------------------------------------------------------------------------------- /utils/demo/chartsData.ts: -------------------------------------------------------------------------------- 1 | export interface ILegends{ 2 | title: string 3 | color: string 4 | } 5 | 6 | 7 | export const doughnutLegends: ILegends[] = [ 8 | { title: 'Shirts', color: 'bg-blue-500' }, 9 | { title: 'Shoes', color: 'bg-teal-600' }, 10 | { title: 'Bags', color: 'bg-purple-600' }, 11 | ] 12 | 13 | export const lineLegends: ILegends[] = [ 14 | { title: 'Organic', color: 'bg-teal-600' }, 15 | { title: 'Paid', color: 'bg-purple-600' }, 16 | ] 17 | 18 | export const barLegends: ILegends[] = [ 19 | { title: 'Shoes', color: 'bg-teal-600' }, 20 | { title: 'Bags', color: 'bg-purple-600' }, 21 | ] 22 | 23 | export const doughnutOptions = { 24 | data: { 25 | datasets: [ 26 | { 27 | data: [33, 33, 33], 28 | /** 29 | * These colors come from Tailwind CSS palette 30 | * https://tailwindcss.com/docs/customizing-colors/#default-color-palette 31 | */ 32 | backgroundColor: ['#0694a2', '#1c64f2', '#7e3af2'], 33 | label: 'Dataset 1', 34 | }, 35 | ], 36 | labels: ['Shoes', 'Shirts', 'Bags'], 37 | }, 38 | options: { 39 | responsive: true, 40 | cutoutPercentage: 80, 41 | }, 42 | legend: { 43 | display: false, 44 | }, 45 | } 46 | 47 | export const lineOptions = { 48 | data: { 49 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 50 | datasets: [ 51 | { 52 | label: 'Organic', 53 | /** 54 | * These colors come from Tailwind CSS palette 55 | * https://tailwindcss.com/docs/customizing-colors/#default-color-palette 56 | */ 57 | backgroundColor: '#0694a2', 58 | borderColor: '#0694a2', 59 | data: [43, 48, 40, 54, 67, 73, 70], 60 | fill: false, 61 | }, 62 | { 63 | label: 'Paid', 64 | fill: false, 65 | /** 66 | * These colors come from Tailwind CSS palette 67 | * https://tailwindcss.com/docs/customizing-colors/#default-color-palette 68 | */ 69 | backgroundColor: '#7e3af2', 70 | borderColor: '#7e3af2', 71 | data: [24, 50, 64, 74, 52, 51, 65], 72 | }, 73 | ], 74 | }, 75 | options: { 76 | responsive: true, 77 | tooltips: { 78 | mode: 'index', 79 | intersect: false, 80 | }, 81 | scales: { 82 | x: { 83 | display: true, 84 | scaleLabel: { 85 | display: true, 86 | labelString: 'Month', 87 | }, 88 | }, 89 | y: { 90 | display: true, 91 | scaleLabel: { 92 | display: true, 93 | labelString: 'Value', 94 | }, 95 | }, 96 | }, 97 | }, 98 | legend: { 99 | display: false, 100 | }, 101 | } 102 | 103 | export const barOptions = { 104 | data: { 105 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 106 | datasets: [ 107 | { 108 | label: 'Shoes', 109 | backgroundColor: '#0694a2', 110 | // borderColor: window.chartColors.red, 111 | borderWidth: 1, 112 | data: [-3, 14, 52, 74, 33, 90, 70], 113 | }, 114 | { 115 | label: 'Bags', 116 | backgroundColor: '#7e3af2', 117 | // borderColor: window.chartColors.blue, 118 | borderWidth: 1, 119 | data: [66, 33, 43, 12, 54, 62, 84], 120 | }, 121 | ], 122 | }, 123 | options: { 124 | responsive: true, 125 | }, 126 | legend: { 127 | display: false, 128 | }, 129 | } 130 | 131 | -------------------------------------------------------------------------------- /example/components/Sidebar/SidebarSubmenu.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react' 2 | import Link from 'next/link' 3 | import { useRouter } from 'next/router' 4 | import { DropdownIcon, IIcon } from 'icons' 5 | import * as Icons from 'icons' 6 | import { Transition } from '@roketid/windmill-react-ui' 7 | import { IRoute, routeIsActive } from 'routes/sidebar' 8 | import SidebarContext from 'context/SidebarContext' 9 | 10 | function Icon({ icon, ...props }: IIcon) { 11 | // @ts-ignore 12 | const _Icon = Icons[icon] 13 | return <_Icon {...props} /> 14 | } 15 | 16 | interface ISidebarSubmenu { 17 | route: IRoute 18 | linkClicked: () => void 19 | } 20 | 21 | function SidebarSubmenu({ route, linkClicked }: ISidebarSubmenu) { 22 | const { pathname } = useRouter() 23 | const { saveScroll } = useContext(SidebarContext) 24 | 25 | const [isDropdownMenuOpen, setIsDropdownMenuOpen] = useState( 26 | route.routes 27 | ? route.routes.filter((r) => { 28 | return routeIsActive(pathname, r) 29 | }).length > 0 30 | : false 31 | ) 32 | 33 | function handleDropdownMenuClick() { 34 | setIsDropdownMenuOpen(!isDropdownMenuOpen) 35 | } 36 | 37 | return ( 38 |
  • 39 | {isDropdownMenuOpen && ( 40 | 44 | )} 45 | 61 | 70 |
      74 | { 75 | route.routes && route.routes.map((r) => ( 76 |
    • 80 | 84 | 92 | {r.name} 93 | 94 | 95 |
    • 96 | )) 97 | } 98 |
    99 |
    100 |
  • 101 | ) 102 | } 103 | 104 | export default SidebarSubmenu 105 | -------------------------------------------------------------------------------- /pages/example/password-reset/[token].tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react' 2 | import Image from 'next/image' 3 | 4 | import { Label, Input, Button, WindmillContext } from '@roketid/windmill-react-ui' 5 | import { useAuth } from 'hooks/auth' 6 | import { useRouter } from 'next/router' 7 | 8 | interface IEvent { 9 | preventDefault: () => void 10 | } 11 | 12 | function PasswordReset() { 13 | const { query } = useRouter() 14 | 15 | const { resetPassword } = useAuth({ middleware: 'guest' }) 16 | 17 | const [submitting, setSubmittingState] = useState(false) 18 | const [email, setEmail] = useState('') 19 | const [password, setPassword] = useState('') 20 | const [password_confirmation, setPasswordConfirmation] = useState('') 21 | const [errors, setErrors] = useState([]) 22 | const [status, setStatus] = useState(null) 23 | 24 | const submitForm = (event: IEvent) => { 25 | event.preventDefault() 26 | 27 | setSubmittingState(true) 28 | resetPassword({ 29 | email, 30 | password, 31 | password_confirmation, 32 | setErrors, 33 | setStatus, 34 | }) 35 | setSubmittingState(false) 36 | } 37 | 38 | useEffect(() => { 39 | setEmail(query ? query.email as string : '') 40 | }, [query]) 41 | 42 | const { mode } = useContext(WindmillContext) 43 | const imgSource = mode === 'dark' ? '/assets/img/forgot-password-office-dark.jpeg' : '/assets/img/forgot-password-office.jpeg' 44 | 45 | return ( 46 |
    47 |
    48 |
    49 |
    50 | 57 |
    58 |
    59 |
    60 |

    61 | Reset Password 62 |

    63 | { 64 | errors.length && !submitting 65 | ?
    {errors[0]}
    66 | : `` 67 | } 68 | {status && ( 69 |
    70 | {status} 71 |
    72 | )} 73 | 74 | 78 | 82 | 86 | 87 | 90 |
    91 |
    92 |
    93 |
    94 |
    95 | ) 96 | } 97 | 98 | export default PasswordReset 99 | -------------------------------------------------------------------------------- /pages/example/verify-email.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react' 2 | import Image from 'next/image' 3 | 4 | import { Button, WindmillContext } from '@roketid/windmill-react-ui' 5 | import { useAuth } from 'hooks/auth' 6 | import Loader from 'example/components/Loader/Loader' 7 | import { useRouter } from 'next/router' 8 | 9 | function ForgotPassword() { 10 | const router = useRouter() 11 | 12 | const { loading, user, redirectedUrl, logout, resendEmailVerification } = useAuth({ 13 | middleware: 'auth', 14 | }) 15 | 16 | const [submitting, setSubmittingState] = useState(false) 17 | const [status, setStatus] = useState(null) 18 | const [errors, setErrors] = useState([]) 19 | 20 | const { mode } = useContext(WindmillContext) 21 | const imgSource = mode === 'dark' ? '/assets/img/mailbox-dark.jpg' : '/assets/img/mailbox.jpg' 22 | 23 | useEffect(() => { 24 | if (errors.length === 0 && user && user.email_verified_at) { 25 | router.push(redirectedUrl) 26 | } 27 | }) 28 | 29 | return loading || user 30 | ? 31 | : ( 32 |
    33 |
    34 |
    35 |
    36 | 43 |
    44 |
    45 |
    46 |

    47 | Verify Email 48 |

    49 | { 50 | errors.length && !submitting 51 | ?
    {errors[0]}
    52 | : `` 53 | } 54 |
    55 | Thanks for signing up! Before getting started, could you 56 | verify your email address by clicking on the link we just 57 | emailed to you? If you didn't receive the email, we will 58 | gladly send you another. 59 |
    60 | 61 | {status === 'verification-link-sent' && ( 62 |
    63 | A new verification link has been sent to the email 64 | address you provided during registration. 65 |
    66 | )} 67 | 68 |
    69 | 79 | 80 | 86 |
    87 |
    88 |
    89 |
    90 |
    91 |
    92 | ) 93 | } 94 | 95 | export default ForgotPassword 96 | -------------------------------------------------------------------------------- /pages/example/cards.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Card, CardBody } from '@roketid/windmill-react-ui' 4 | import PageTitle from 'example/components/Typography/PageTitle' 5 | import SectionTitle from 'example/components/Typography/SectionTitle' 6 | import CTA from 'example/components/CTA' 7 | import InfoCard from 'example/components/Cards/InfoCard' 8 | import RoundIcon from 'example/components/RoundIcon' 9 | import Layout from 'example/containers/Layout' 10 | import { CartIcon, ChatIcon, MoneyIcon, PeopleIcon, StarIcon } from 'icons' 11 | 12 | function Cards() { 13 | return ( 14 | 15 | Cards 16 | 17 | 18 | 19 | Big section cards 20 | 21 | 22 | 23 |

    24 | Large, full width sections goes here 25 |

    26 |
    27 |
    28 | 29 | Responsive cards 30 | 31 |
    32 | 33 | {/* @ts-ignore */} 34 | 40 | 41 | 42 | 43 | {/* @ts-ignore */} 44 | 50 | 51 | 52 | 53 | {/* @ts-ignore */} 54 | 60 | 61 | 62 | 63 | {/* @ts-ignore */} 64 | 70 | 71 |
    72 | 73 | Cards with title 74 | 75 |
    76 | 77 | 78 |

    Revenue

    79 |

    80 | Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fuga, cum commodi a omnis 81 | numquam quod? Totam exercitationem quos hic ipsam at qui cum numquam, sed amet 82 | ratione! Ratione, nihil dolorum. 83 |

    84 |
    85 |
    86 | 87 | 88 | 89 |

    Colored card

    90 |

    91 | Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fuga, cum commodi a omnis 92 | numquam quod? Totam exercitationem quos hic ipsam at qui cum numquam, sed amet 93 | ratione! Ratione, nihil dolorum. 94 |

    95 |
    96 |
    97 |
    98 |
    99 | ) 100 | } 101 | 102 | export default Cards 103 | -------------------------------------------------------------------------------- /pages/example/buttons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from '@roketid/windmill-react-ui' 3 | import PageTitle from 'example/components/Typography/PageTitle' 4 | import SectionTitle from 'example/components/Typography/SectionTitle' 5 | import CTA from 'example/components/CTA' 6 | import Layout from 'example/containers/Layout' 7 | import { HeartIcon, EditIcon, StarIcon } from 'icons' 8 | 9 | function Buttons() { 10 | return ( 11 | 12 | Buttons 13 | 14 | 15 | 16 | Primary 17 |
    18 |
    19 | 20 |
    21 | 22 |
    23 | 24 |
    25 | 26 |
    27 | 28 |
    29 | 30 | {/*
    31 | 34 |
    */} 35 | 36 |
    37 | 38 |
    39 | 40 |
    41 | 42 |
    43 |
    44 | 45 | Outline 46 |
    47 |
    48 | 51 |
    52 | 53 |
    54 | 57 |
    58 | 59 |
    60 | 61 |
    62 | 63 | {/*
    64 | 67 |
    */} 68 | 69 |
    70 | 73 |
    74 | 75 |
    76 | 79 |
    80 |
    81 | 82 | Link 83 |
    84 |
    85 | 88 |
    89 | 90 |
    91 | 94 |
    95 | 96 |
    97 | 98 |
    99 | 100 | {/*
    101 | 104 |
    */} 105 | 106 |
    107 | 110 |
    111 | 112 |
    113 | 116 |
    117 |
    118 | 119 | Icons 120 |
    121 |
    122 | {/* @ts-ignore */} 123 | 126 |
    127 | 128 |
    129 | {/* @ts-ignore */} 130 | 133 |
    134 | 135 |
    136 | {/* @ts-ignore */} 137 |
    139 | 140 |
    141 | {/* @ts-ignore */} 142 |
    144 | 145 |
    146 | {/* @ts-ignore */} 147 |
    149 |
    150 | {/* @ts-ignore */} 151 |
    153 |
    154 |
    155 | ) 156 | } 157 | 158 | export default Buttons 159 | -------------------------------------------------------------------------------- /pages/example/create-account.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react' 2 | import Link from 'next/link' 3 | import Image from 'next/image' 4 | 5 | import { Input, Label, Button, WindmillContext } from '@roketid/windmill-react-ui' 6 | import { useAuth } from 'hooks/auth' 7 | import Loader from 'example/components/Loader/Loader' 8 | 9 | interface IEvent { 10 | preventDefault: () => void 11 | } 12 | 13 | function CrateAccount() { 14 | const { register, loading, user } = useAuth({ 15 | middleware: 'guest', 16 | redirectIfAuthenticated: '/example', 17 | }) 18 | 19 | const [submitting, setSubmittingState] = useState(false) 20 | const [name, setName] = useState('') 21 | const [email, setEmail] = useState('') 22 | const [password, setPassword] = useState('') 23 | const [password_confirmation, setPasswordConfirmation] = useState('') 24 | const [errors, setErrors] = useState([]) 25 | const [status, setStatus] = useState(null) 26 | 27 | const submitForm = async (event: IEvent) => { 28 | event.preventDefault() 29 | 30 | setSubmittingState(true) 31 | register({ name, email, password, password_confirmation, setErrors, setStatus }) 32 | setSubmittingState(false) 33 | } 34 | 35 | const { mode } = useContext(WindmillContext) 36 | const imgSource = mode === 'dark' ? '/assets/img/create-account-office-dark.jpeg' : '/assets/img/create-account-office.jpeg' 37 | 38 | return loading || user 39 | ? () 40 | : ( 41 |
    42 |
    43 |
    44 |
    45 | 52 |
    53 |
    54 |
    55 |

    56 | Create account 57 |

    58 | { 59 | errors.length && !submitting 60 | ?
    {errors[0]}
    61 | : `` 62 | } 63 | 64 | 68 | 72 | 76 | 80 | 81 | 87 | 88 | 91 | 92 |
    93 | 94 |

    95 | 96 | 99 | Already have an account? Login 100 | 101 | 102 |

    103 |
    104 |
    105 |
    106 |
    107 |
    108 | ) 109 | } 110 | 111 | export default CrateAccount 112 | -------------------------------------------------------------------------------- /pages/example/login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import Link from 'next/link' 3 | import Image from 'next/image' 4 | 5 | import { Label, Input, Button, WindmillContext } from '@roketid/windmill-react-ui' 6 | import { useAuth } from 'hooks/auth' 7 | import Loader from 'example/components/Loader/Loader' 8 | import { useRouter } from 'next/router' 9 | 10 | interface IEvent { 11 | preventDefault: () => void 12 | } 13 | 14 | function LoginPage() { 15 | const [showLoader, setShowLoaderState] = useState(true) 16 | 17 | const { query } = useRouter() 18 | const { login, loading, user } = useAuth({ 19 | middleware: 'guest', 20 | redirectIfAuthenticated: query.next_to as string, 21 | }) 22 | 23 | const [submitting, setSubmittingState] = useState(false) 24 | const [email, setEmail] = useState('') 25 | const [password, setPassword] = useState('') 26 | const [errors, setErrors] = useState([]) 27 | const [status, setStatus] = useState(null) 28 | 29 | useEffect(() => { 30 | const reset = query && query.reset ? query.reset as string : '' 31 | if (reset.length > 0 && errors.length === 0) { 32 | setStatus(atob(reset)) 33 | } else { 34 | setStatus(null) 35 | } 36 | }, [query, errors]) 37 | 38 | useEffect(() => { 39 | if (!loading && !user) { 40 | setShowLoaderState(false) 41 | } 42 | 43 | return () => { 44 | setShowLoaderState(true) 45 | } 46 | }, [loading, user]) 47 | 48 | const submitForm = async (event: IEvent) => { 49 | event.preventDefault() 50 | 51 | setSubmittingState(true) 52 | login({ email, password, setErrors, setStatus }) 53 | setSubmittingState(false) 54 | } 55 | 56 | const { mode } = useContext(WindmillContext) 57 | const imgSource = mode === 'dark' ? '/assets/img/login-office-dark.jpeg' : '/assets/img/login-office.jpeg' 58 | 59 | return showLoader 60 | ? () 61 | : ( 62 |
    63 |
    64 |
    65 |
    66 | 73 |
    74 |
    75 |
    76 |

    77 | Login 78 |

    79 | { 80 | errors.length && !submitting 81 | ?
    {errors[0]}
    82 | : `` 83 | } 84 | {status && ( 85 |
    86 | {status} 87 |
    88 | )} 89 | 90 | 101 | 102 | 113 | 114 | 117 | 118 |
    119 | 120 |

    121 | 122 | 123 | Forgot your password? 124 | 125 | 126 |

    127 |

    128 | 129 | 130 | Create account 131 | 132 | 133 |

    134 |
    135 |
    136 |
    137 |
    138 |
    139 | ); 140 | } 141 | 142 | export default LoginPage 143 | -------------------------------------------------------------------------------- /example/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from 'react' 2 | import SidebarContext from 'context/SidebarContext' 3 | import { 4 | SearchIcon, 5 | MoonIcon, 6 | SunIcon, 7 | BellIcon, 8 | MenuIcon, 9 | OutlinePersonIcon, 10 | OutlineCogIcon, 11 | OutlineLogoutIcon, 12 | } from 'icons' 13 | import { Avatar, Badge, Input, Dropdown, DropdownItem, WindmillContext } from '@roketid/windmill-react-ui' 14 | 15 | interface IUser { 16 | [key: string]: any 17 | } 18 | interface IHeader { 19 | user?: IUser 20 | logout?: Function 21 | } 22 | 23 | function Header({user, logout}: IHeader) { 24 | const { mode, toggleMode } = useContext(WindmillContext) 25 | const { toggleSidebar } = useContext(SidebarContext) 26 | 27 | const [isNotificationsMenuOpen, setIsNotificationsMenuOpen] = useState(false) 28 | const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false) 29 | 30 | function handleNotificationsClick() { 31 | setIsNotificationsMenuOpen(!isNotificationsMenuOpen) 32 | } 33 | 34 | function handleProfileClick() { 35 | setIsProfileMenuOpen(!isProfileMenuOpen) 36 | } 37 | 38 | return ( 39 |
    40 |
    41 | {/* */} 42 | 49 | {/* */} 50 |
    51 |
    52 |
    53 |
    55 | 60 |
    61 |
    62 |
      63 | {/* */} 64 |
    • 65 | 76 |
    • 77 | {/* */} 78 |
    • 79 | 92 | 93 | setIsNotificationsMenuOpen(false)} 97 | > 98 | 99 | Messages 100 | 13 101 | 102 | 103 | Sales 104 | 2 105 | 106 | alert('Alerts!')}> 107 | Alerts 108 | 109 | 110 |
    • 111 | {/* */} 112 |
    • 113 | 126 | setIsProfileMenuOpen(false)} 130 | > 131 | 132 | 135 | 136 | 139 | logout && logout()}> 140 | 143 | 144 |
    • 145 |
    146 |
    147 |
    148 | ) 149 | } 150 | 151 | export default Header 152 | -------------------------------------------------------------------------------- /hooks/auth.ts: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr' 2 | import { useCallback, useEffect, useState } from 'react' 3 | import { useRouter } from 'next/router' 4 | import { ParsedUrlQuery, stringify } from 'querystring'; 5 | import api, { csrf } from 'lib/api' 6 | 7 | declare type AuthMiddleware = 'auth' | 'guest' 8 | 9 | interface IUseAuth { 10 | middleware: AuthMiddleware 11 | redirectIfAuthenticated?: string 12 | } 13 | 14 | interface IApiRequest { 15 | setErrors: React.Dispatch> 16 | setStatus: React.Dispatch> 17 | [key: string]: any 18 | } 19 | 20 | export const useAuth = (config: IUseAuth) => { 21 | const loginUrl = process.env.NEXT_PUBLIC_AUTH_PAGE_LOGIN || '/example/login' 22 | const verifyEmailUrl = process.env.NEXT_PUBLIC_AUTH_LOGIN || '/example/verify-email' 23 | const redirectedUrl = process.env.NEXT_PUBLIC_AUTH_REDIRECTED || '/example' 24 | 25 | const apiPathUser = process.env.NEXT_PUBLIC_API_PATH_USER || '/api/user' 26 | const apiPathLogin = process.env.NEXT_PUBLIC_API_PATH_LOGIN || '/login' 27 | const apiPathRegister = process.env.NEXT_PUBLIC_API_PATH_REGISTER || '/register' 28 | const apiPathForgotPassword = process.env.NEXT_PUBLIC_API_PATH_FORGOT_PASSWORD || '/forgot-password' 29 | const apiPathResetPassword = process.env.NEXT_PUBLIC_API_PATH_RESET_PASSWORD || '/reset-password' 30 | const apiPathVerifyEmail = process.env.NEXT_PUBLIC_API_PATH_VERIFY_EMAIL || '/email/verification-notification' 31 | const apiPathLogout = process.env.NEXT_PUBLIC_API_PATH_LOGIN || '/logout' 32 | 33 | const router = useRouter() 34 | 35 | const {middleware, redirectIfAuthenticated} = config 36 | const redirectIfAuthenticatedUrl = redirectIfAuthenticated || redirectedUrl 37 | 38 | 39 | const [loading, setLoadingState] = useState(true) 40 | 41 | const { data: user, error, mutate } = useSWR(apiPathUser, () => 42 | api 43 | .get(apiPathUser) 44 | .then(res => res.data) 45 | .catch(error => { 46 | if (error.response.status !== 409) throw error 47 | 48 | router.push(redirectIfAuthenticatedUrl.indexOf('verify-email') < 0 ? verifyEmailUrl : redirectIfAuthenticatedUrl) 49 | }), 50 | ) 51 | 52 | const register = async (args: IApiRequest) => { 53 | const { setErrors, ...props } = args 54 | await csrf() 55 | setErrors([]) 56 | 57 | api 58 | .post(apiPathRegister, props) 59 | .then(() => mutate()) 60 | .catch(error => { 61 | if (error.response.status !== 422) throw error 62 | 63 | setErrors(Object.values(error.response.data.errors).flat() as never[]) 64 | }) 65 | } 66 | 67 | const login = async (args: IApiRequest) => { 68 | const { setErrors, setStatus, ...props } = args 69 | await csrf() 70 | setErrors([]) 71 | setStatus(null) 72 | 73 | api 74 | .post(apiPathLogin, props) 75 | .then(() => mutate()) 76 | .catch(error => { 77 | if (error.response.status !== 422) throw error 78 | 79 | setErrors(Object.values(error.response.data.errors).flat() as never[]) 80 | }) 81 | } 82 | 83 | const forgotPassword = async (args: IApiRequest) => { 84 | const { setErrors, setStatus, email } = args 85 | await csrf() 86 | setErrors([]) 87 | setStatus(null) 88 | 89 | api 90 | .post(apiPathForgotPassword, { email }) 91 | .then(response => setStatus(response.data.status)) 92 | .catch(error => { 93 | if (error.response.status !== 422) throw error 94 | 95 | setErrors(Object.values(error.response.data.errors).flat() as never[]) 96 | }) 97 | } 98 | 99 | const resetPassword = async (args: IApiRequest) => { 100 | const { setErrors, setStatus, ...props } = args 101 | await csrf() 102 | setErrors([]) 103 | setStatus(null) 104 | 105 | api 106 | .post(apiPathResetPassword, { token: router.query.token, ...props }) 107 | .then(response => router.push(loginUrl + '?reset=' + btoa(response.data.status))) 108 | .catch(error => { 109 | if (error.response.status !== 422) throw error 110 | 111 | setErrors(Object.values(error.response.data.errors).flat() as never[]) 112 | }) 113 | } 114 | 115 | const resendEmailVerification = (args: IApiRequest) => { 116 | const { setStatus } = args 117 | 118 | api 119 | .post(apiPathVerifyEmail) 120 | .then(response => setStatus(response.data.status)) 121 | } 122 | 123 | const logout = useCallback(async (nextUrl?: string, nextQuery?: ParsedUrlQuery) => { 124 | if (! error) { 125 | await api.post(apiPathLogout) 126 | 127 | mutate() 128 | } 129 | 130 | let wrapNextUrl = loginUrl as string 131 | 132 | if (nextUrl) { 133 | const queryString = stringify(nextQuery) 134 | wrapNextUrl = encodeURIComponent(nextUrl + (queryString ? '?' + queryString : '')) 135 | } 136 | 137 | window.location.assign(loginUrl + (nextUrl && nextUrl.indexOf(loginUrl) < 0 && nextUrl.indexOf(verifyEmailUrl) < 0 ? '?next_to=' + wrapNextUrl : '')) 138 | }, [mutate, error, loginUrl, apiPathLogout, verifyEmailUrl]) 139 | 140 | useEffect(() => { 141 | if (middleware === 'guest' && user) { 142 | const toUrl = redirectIfAuthenticatedUrl + ( 143 | redirectIfAuthenticatedUrl.indexOf('logged=1') > 0 ? '' : (redirectIfAuthenticatedUrl.indexOf('?') > 0 ? '&' : '?') + 'logged=1' 144 | ) 145 | router.push(toUrl, redirectIfAuthenticatedUrl) 146 | } 147 | if (middleware === 'auth' && error) logout(router.pathname, router.query) 148 | setLoadingState(false) 149 | 150 | return () => { 151 | setLoadingState(true) 152 | } 153 | }, [middleware, redirectIfAuthenticatedUrl, router, user, error, logout]) 154 | 155 | return { 156 | loginUrl, 157 | verifyEmailUrl, 158 | redirectedUrl, 159 | apiPathUser, 160 | apiPathLogin, 161 | apiPathRegister, 162 | apiPathForgotPassword, 163 | apiPathResetPassword, 164 | apiPathVerifyEmail, 165 | apiPathLogout, 166 | 167 | loading, 168 | user, 169 | register, 170 | login, 171 | forgotPassword, 172 | resetPassword, 173 | resendEmailVerification, 174 | logout 175 | } 176 | } -------------------------------------------------------------------------------- /pages/example/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Doughnut, Line } from 'react-chartjs-2' 3 | 4 | import CTA from 'example/components/CTA' 5 | import InfoCard from 'example/components/Cards/InfoCard' 6 | import ChartCard from 'example/components/Chart/ChartCard' 7 | import ChartLegend from 'example/components/Chart/ChartLegend' 8 | import PageTitle from 'example/components/Typography/PageTitle' 9 | import RoundIcon from 'example/components/RoundIcon' 10 | import Layout from 'example/containers/Layout' 11 | import response, { ITableData } from 'utils/demo/tableData' 12 | import { ChatIcon, CartIcon, MoneyIcon, PeopleIcon, StarIcon } from 'icons' 13 | 14 | import { 15 | TableBody, 16 | TableContainer, 17 | Table, 18 | TableHeader, 19 | TableCell, 20 | TableRow, 21 | TableFooter, 22 | Avatar, 23 | Badge, 24 | Pagination, 25 | } from '@roketid/windmill-react-ui' 26 | 27 | import { 28 | doughnutOptions, 29 | lineOptions, 30 | doughnutLegends, 31 | lineLegends, 32 | } from 'utils/demo/chartsData' 33 | 34 | import { 35 | Chart, 36 | ArcElement, 37 | CategoryScale, 38 | LinearScale, 39 | PointElement, 40 | LineElement, 41 | Title, 42 | Tooltip, 43 | Legend, 44 | } from 'chart.js' 45 | import { useRouter } from 'next/router' 46 | 47 | function Dashboard() { 48 | Chart.register( 49 | ArcElement, 50 | CategoryScale, 51 | LinearScale, 52 | PointElement, 53 | LineElement, 54 | Title, 55 | Tooltip, 56 | Legend 57 | ) 58 | 59 | const [page, setPage] = useState(1) 60 | const [data, setData] = useState([]) 61 | 62 | // pagination setup 63 | const resultsPerPage = 10 64 | const totalResults = response.length 65 | 66 | // pagination change control 67 | function onPageChange(p: number) { 68 | setPage(p) 69 | } 70 | 71 | // on page change, load new sliced data 72 | // here you would make another server request for new data 73 | useEffect(() => { 74 | setData(response.slice((page - 1) * resultsPerPage, page * resultsPerPage)) 75 | }, [page]) 76 | 77 | let ctaMessage = 'Star this project on GitHub' as string 78 | let ctaUrl = 'https://github.com/roketid/windmill-nextjs-laravel-breeze' as string|undefined 79 | let ctaIcon = StarIcon 80 | 81 | const { query } = useRouter() 82 | 83 | if (query.verified) { 84 | ctaMessage = 'Welcome, your email has been verified' 85 | ctaUrl = undefined 86 | } else if (query.logged) { 87 | ctaMessage = 'Welcome back' 88 | ctaUrl = undefined 89 | } 90 | 91 | return ( 92 | 93 | Dashboard 94 | 95 | 96 | 97 | {/* */} 98 |
    99 | 100 | {/* @ts-ignore */} 101 | 107 | 108 | 109 | 110 | {/* @ts-ignore */} 111 | 117 | 118 | 119 | 120 | {/* @ts-ignore */} 121 | 127 | 128 | 129 | 130 | {/* @ts-ignore */} 131 | 137 | 138 |
    139 | 140 | 141 | 142 | 143 | 144 | Client 145 | Amount 146 | Status 147 | Date 148 | 149 | 150 | 151 | {data.map((user, i) => ( 152 | 153 | 154 |
    155 | 160 |
    161 |

    {user.name}

    162 |

    163 | {user.job} 164 |

    165 |
    166 |
    167 |
    168 | 169 | $ {user.amount} 170 | 171 | 172 | {user.status} 173 | 174 | 175 | 176 | {new Date(user.date).toLocaleDateString()} 177 | 178 | 179 |
    180 | ))} 181 |
    182 |
    183 | 184 | 190 | 191 |
    192 | 193 | Charts 194 |
    195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 |
    205 |
    206 | ) 207 | } 208 | 209 | export default Dashboard 210 | -------------------------------------------------------------------------------- /pages/example/tables.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | 3 | import PageTitle from 'example/components/Typography/PageTitle' 4 | import SectionTitle from 'example/components/Typography/SectionTitle' 5 | import CTA from 'example/components/CTA' 6 | import { 7 | Table, 8 | TableHeader, 9 | TableCell, 10 | TableBody, 11 | TableRow, 12 | TableFooter, 13 | TableContainer, 14 | Badge, 15 | Avatar, 16 | Button, 17 | Pagination, 18 | } from '@roketid/windmill-react-ui' 19 | import { EditIcon, StarIcon, TrashIcon } from 'icons' 20 | 21 | import response, { ITableData } from 'utils/demo/tableData' 22 | import Layout from 'example/containers/Layout' 23 | // make a copy of the data, for the second table 24 | const response2 = response.concat([]) 25 | 26 | function Tables() { 27 | /** 28 | * DISCLAIMER: This code could be badly improved, but for the sake of the example 29 | * and readability, all the logic for both table are here. 30 | * You would be better served by dividing each table in its own 31 | * component, like Table(?) and TableWithActions(?) hiding the 32 | * presentation details away from the page view. 33 | */ 34 | 35 | // setup pages control for every table 36 | const [pageTable1, setPageTable1] = useState(1) 37 | const [pageTable2, setPageTable2] = useState(1) 38 | 39 | // setup data for every table 40 | const [dataTable1, setDataTable1] = useState([]) 41 | const [dataTable2, setDataTable2] = useState([]) 42 | 43 | // pagination setup 44 | const resultsPerPage = 10 45 | const totalResults = response.length 46 | 47 | // pagination change control 48 | function onPageChangeTable1(p: number) { 49 | setPageTable1(p) 50 | } 51 | 52 | // pagination change control 53 | function onPageChangeTable2(p: number) { 54 | setPageTable2(p) 55 | } 56 | 57 | // on page change, load new sliced data 58 | // here you would make another server request for new data 59 | useEffect(() => { 60 | setDataTable1(response.slice((pageTable1 - 1) * resultsPerPage, pageTable1 * resultsPerPage)) 61 | }, [pageTable1]) 62 | 63 | // on page change, load new sliced data 64 | // here you would make another server request for new data 65 | useEffect(() => { 66 | setDataTable2(response2.slice((pageTable2 - 1) * resultsPerPage, pageTable2 * resultsPerPage)) 67 | }, [pageTable2]) 68 | 69 | return ( 70 | 71 | Tables 72 | 73 | 74 | 75 | Simple table 76 | 77 | 78 | 79 | 80 | Client 81 | Amount 82 | Status 83 | Date 84 | 85 | 86 | 87 | {dataTable1.map((user, i) => ( 88 | 89 | 90 |
    91 | 92 |
    93 |

    {user.name}

    94 |

    {user.job}

    95 |
    96 |
    97 |
    98 | 99 | $ {user.amount} 100 | 101 | 102 | {user.status} 103 | 104 | 105 | {new Date(user.date).toLocaleDateString()} 106 | 107 |
    108 | ))} 109 |
    110 |
    111 | 112 | 118 | 119 |
    120 | 121 | Table with actions 122 | 123 | 124 | 125 | 126 | Client 127 | Amount 128 | Status 129 | Date 130 | Actions 131 | 132 | 133 | 134 | {dataTable2.map((user, i) => ( 135 | 136 | 137 |
    138 | 139 |
    140 |

    {user.name}

    141 |

    {user.job}

    142 |
    143 |
    144 |
    145 | 146 | $ {user.amount} 147 | 148 | 149 | {user.status} 150 | 151 | 152 | {new Date(user.date).toLocaleDateString()} 153 | 154 | 155 |
    156 | 159 | 162 |
    163 |
    164 |
    165 | ))} 166 |
    167 |
    168 | 169 | 175 | 176 |
    177 |
    178 | ) 179 | } 180 | 181 | export default Tables 182 | -------------------------------------------------------------------------------- /pages/example/forms.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Input, HelperText, Label, Select, Textarea } from '@roketid/windmill-react-ui' 4 | import CTA from 'example/components/CTA' 5 | import PageTitle from 'example/components/Typography/PageTitle' 6 | import SectionTitle from 'example/components/Typography/SectionTitle' 7 | 8 | import Layout from 'example/containers/Layout' 9 | import { MailIcon, StarIcon } from 'icons' 10 | 11 | function Forms() { 12 | return ( 13 | 14 | Forms 15 | 16 | Elements 17 | 18 |
    19 | 23 | 24 | 28 | 29 |
    30 | {/* TODO: Check if this label is accessible, or fallback */} 31 | {/* Account Type */} 32 | 33 |
    34 | 38 | 42 | 46 |
    47 |
    48 | 49 | 58 | 59 | 69 | 70 |