Added Blog Page and Housekeeping

This commit is contained in:
Ahmad 2024-11-17 16:28:51 -05:00
parent db4bf103c3
commit d32415232e
No known key found for this signature in database
GPG key ID: 8FD8A93530D182BF
18 changed files with 6860 additions and 769 deletions

1
.gitignore vendored
View file

@ -36,3 +36,4 @@ next-env.d.ts
# other
/.vscode/
/.content-collections

View file

@ -42,3 +42,4 @@ next-env.d.ts
package.json
yarn.lock
README.md
/.content-collections

View file

@ -5,11 +5,14 @@
## About
Tasko is a company that helps you manage your tasks efficiently by using kanban boards. Kanban boards are a visual way of organizing your work into different stages, such as to-do, in progress, and done. With Tasko, you can create and customize your own kanban boards, add tasks, and collaborate with your team members. Tasko is designed to be simple, intuitive, and flexible, so you can focus on getting things done and achieving your goals.
Tasko is a website that helps you manage your tasks efficiently by using kanban boards. Kanban boards are a visual way of organizing your work into different stages, such as to-do, in progress, and done. With Tasko, you can create and customize your own kanban boards and add tasks. Tasko is designed to be simple, intuitive, and flexible, so you can focus on getting things done and achieving your goals. [Try Tasko Today!](https://tasko-omega.vercel.app/)
## Documentation
Currently, there is no documentation or wiki available but there will be one added in the future.
> [!WARNING]
> Documentation is not complete and is still a work in progress.
Documentation can be found on our new, [Mintlify page](https://tasko.mintlify.app/).
## Roadmap
@ -22,11 +25,30 @@ This will be published on the site some time soon but for now, the roadmap will
- [ ] Add end-to-end Database encryption (for customer data such as card titles and descriptions, and subscription information)
- [ ] Add dark mode
- [ ] Markdown Support in Card Descriptions
- [ ] Self-Hosted Version
## Contributing and Development
### Development Commands
Start Dev Server: ``yarn dev``
Production Build: ``yarn build``
Start Production Server: ``yarn start``
Linting: ``yarn lint``
Check Formatting: ``yarn format``
Fix Formatting: ``yarn format:fix``
Please make sure to lint and check formatting (using the commands listed above) before submitting a Pull Request.
## Legal
[Privacy Policy](https://tasko-omega.vercel.app/privacy-policy)
[Privacy Policy](https://tasko-omega.vercel.app/privacy-policy) _Temporarily removed to revamp it._
[Terms of Service](https://tasko-omega.vercel.app/terms-of-service) _The Terms of Service will be added once a CMS is added and implemented._
[Terms of Service](https://tasko-omega.vercel.app/terms-of-service) _The Terms of Service will be added soon!_
[License](https://github.com/ahmadk953/tasko/blob/main/LICENCE)
[License](https://github.com/ahmadk953/tasko/blob/main/LICENCE) _Will be located on website at some point in time._

View file

@ -8,11 +8,11 @@ export const Footer = () => {
<div className='mx-auto flex w-full items-center justify-between md:max-w-screen-2xl'>
<Logo />
<div className='flex w-full items-center justify-between space-x-4 md:block md:w-auto'>
<Button size='sm' variant='ghost'>
<Link href='/privacy-policy'>Privacy Policy</Link>
<Button className='italic' size='sm' variant='ghost'>
Privacy Policy - Temporarily Removed
</Button>
<Button size='sm' variant='ghost'>
Terms of Service
<Button className='italic' size='sm' variant='ghost'>
Terms of Service - Coming Soon
</Button>
</div>
</div>

View file

@ -13,14 +13,22 @@ export const Navbar = async () => {
<div className='fixed top-0 flex h-14 w-full items-center border-b bg-white px-4 shadow-sm'>
<div className='mx-auto flex w-full items-center justify-between md:max-w-screen-2xl'>
<Logo />
<div className='ml-0 flex w-full flex-auto items-center space-x-1 md:ml-6 md:block md:w-auto md:space-x-4'>
<Button size='sm' variant='ghost' asChild>
<Link href='/blog'>Blog</Link>
</Button>
<Button size='sm' variant='ghost' asChild>
<Link href='https://tasko.mintlify.app/'>Docs</Link>
</Button>
</div>
<div className='flex w-full items-center justify-between space-x-4 md:block md:w-auto'>
{!isSignedIn ? (
<div className='flex w-full justify-between space-x-4 md:block md:w-auto'>
<Button size='sm' variant='outline' asChild>
<Link href='sign-in'>Login</Link>
<Link href='/sign-in'>Login</Link>
</Button>
<Button size='sm' asChild>
<Link href='sign-up'>Get Tasko for Free</Link>
<Link href='/sign-up'>Get Tasko for Free</Link>
</Button>
</div>
) : (

32
app/(main)/blog/page.tsx Normal file
View file

@ -0,0 +1,32 @@
import { allBlogPosts } from 'content-collections';
import Image from 'next/image';
import Link from 'next/link';
const BlogPage = () => {
return (
<div className='ml-4 mr-4 flex flex-col items-center space-y-10'>
<h1 className='text-4xl font-semibold text-neutral-700'>Blog</h1>
<div className='grid grid-cols-2 gap-20 sm:grid-cols-3 lg:grid-cols-4'>
{allBlogPosts.map((post) => (
<div className='space-y-4 text-center' key={post._meta.path}>
<Link href={`blog/posts/${post._meta.path}`}>
<Image
src={post.coverImage}
height={100}
width={300}
alt='post cover image'
className='aspect-video w-full rounded-md object-cover'
/>
<h2 className='text-lg font-semibold text-neutral-700'>
{post.title}
</h2>
<p className='text-sm text-neutral-500'>{post.summary}</p>
</Link>
</div>
))}
</div>
</div>
);
};
export default BlogPage;

View file

@ -0,0 +1,40 @@
import { MDXContent } from '@content-collections/mdx/react';
import { allBlogPosts } from 'content-collections';
import Image from 'next/image';
interface PostPageProps {
params: Promise<{ post: string }>;
}
const PostPage = async (props: PostPageProps) => {
const params = await props.params;
const post = allBlogPosts.find((post) => post._meta.path === params.post);
if (!post) {
return (
<h1 className='text-6xl font-semibold text-neutral-900'>
Post not found
</h1>
);
}
return (
<div className='mx-auto p-4 md:p-6 lg:p-8'>
<Image
className='mb-2 h-64 w-full rounded-md object-cover md:h-[500px]'
src={post.coverImage}
width={1200}
height={600}
alt='post cover image'
/>
<h1 className='mb-12 text-center text-6xl font-bold text-neutral-900'>
{post.title}
</h1>{' '}
<div className='container prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg prose-p:text-xl dark:prose-headings:text-white'>
<MDXContent code={post.mdx} />
</div>
</div>
);
};
export default PostPage;

View file

@ -0,0 +1,14 @@
---
title: 'Welcome to the Blog!'
summary: 'Welcome to our new blog page!'
coverImage: '/hero.svg'
datePublished: ''
---
## Welcome!
Hello and welcome to our new blog page! We are so exited to finally have a place to share updates and news about our app.
## What's next?
As you can see, although we have a blog page, it's a bit basic at the moment. In the future, we are planning to add things such as tags and dates as well as other stuff. In terms of the website it's self, we have a few major feates that we plan to add in the comming months.

View file

@ -1,282 +0,0 @@
import Link from 'next/link';
const PrivacyPolicyPage = () => {
return (
<div className='ml-10 mr-10 content-center text-center'>
<h1 className='pb-10 text-4xl font-bold text-slate-800'>
TASKO PRIVACY POLICY
</h1>
<div>
<h1 className='text-md font-semibold text-slate-800'>
TASKO (the Company) is committed to maintaining robust privacy
protections for its users. Our Privacy Policy (Privacy Policy) is
designed to help you understand how we collect, use and safeguard the
information you provide to us and to assist you in making informed
decisions when using our Service.
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
For purposes of this Agreement, Site refers to the Companys
website, which can be accessed at{' '}
<Link
href='https://tasko-omega.vercel.app'
className='text-blue-600/100 underline'
>
https://tasko-omega.vercel.app
</Link>
. Service refers to the Companys services accessed via the Site, in
which users can create Kanban Boards with lists and cards. The terms
we, us, and our refer to the Company. You refers to you, as a
user of our Site or our Service. By accessing our Site or our Service,
you accept our Privacy Policy and Terms of Use (found here:{' '}
<Link
href='https://tasko-omega.vercel.app/terms-of-service'
className='text-blue-600/100 underline'
>
https://tasko-omega.vercel.app/terms-of-service
</Link>
), and you consent to our collection, storage, use and disclosure of
your Personal Information as described in this Privacy Policy.
</p>
<br />
<h1 className='text-xl font-semibold text-slate-800'>
I. INFORMATION WE COLLECT
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
We collect Non-Personal Information and Personal Information.
Non-Personal Information includes information that cannot be used to
personally identify you, such as anonymous usage data, general
demographic information we may collect, referring/exit pages and URLs,
platform types, preferences you submit and preferences that are
generated based on the data you submit and number of clicks. Personal
Information includes your email and full name (first and last), which
you submit to us through the registration process at the Site. We may
also collect your payment information, billing address, and phone
number in addition if you choose to purchase a paid plan through our
payment processor, Stripe.
</p>
<br />
<h1 className='text-1xl font-semibold text-slate-800'>
1. Information collected via Technology
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
To activate the Service you do not need to submit any Personal
Information other than your email address and full name (first and
last). To use the Service thereafter, you do not need to submit
further Personal Information unless you are purchasing a paid plan.
However, in an effort to improve the quality of the Service, we track
information provided to us by your browser or by our software
application when you view or use the Service, such as the website you
came from (known as the referring URL), the type of browser you use,
the device from which you connected to the Service, the time and date
of access, and other information that does not personally identify
you. We track this information using cookies, or small text files
which include an anonymous unique identifier. Cookies are sent to a
users browser from our servers and are stored on the users computer
hard drive. Sending a cookie to a users browser enables us to collect
Non-Personal information about that user and keep a record of the
users preferences when utilizing our services, both on an individual
and aggregate basis. For example, the Company may use cookies to
collect the following information:
<ul className='pb-2 pt-2'>
<li>- What user agent (browser) you are using</li>
<li>- What device you are currently using</li>
</ul>
The Company may use both persistent and session cookies; persistent
cookies remain on your computer after you close your session and until
you delete them, while session cookies expire when you close your
browser.
</p>
<br />
<h1 className='text-1xl font-semibold text-slate-800'>
2. Information you provide us by registering for an account
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
In addition to the information provided automatically by your browser
when you visit the Site, to become a subscriber to the Service you
will need to create a personal profile. You can create a profile by
registering with the Service and entering your email address, and
creating a user name and a password. By registering, you are
authorizing us to collect, store and use your email address in
accordance with this Privacy Policy.
</p>
<br />
<h1 className='text-1xl font-semibold text-slate-800'>
3. Childrens Privacy
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
The Site and the Service are not directed to anyone under the age of
13. The Site does not knowingly collect or solicit information from
anyone under the age of 13, or allow anyone under the age of 13 to
sign up for the Service. In the event that we learn that we have
gathered personal information from anyone under the age of 13 without
the consent of a parent or guardian, we will delete that information
as soon as possible. If you believe we have collected such
information, please contact us at{' '}
<Link
href='mailto:ahmad.khan@outoforgedu.onmicrosoft.com'
className='text-blue-600/100 underline'
>
ahmad.khan@outoforgedu.onmicrosoft.com
</Link>
.
</p>
<br />
<h1 className='text-xl font-semibold text-slate-800'>
II. HOW WE USE AND SHARE INFORMATION
</h1>
<br />
<h1 className='text-1xl font-semibold text-slate-800'>
Personal Information:
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
Except as otherwise stated in this Privacy Policy, we do not sell,
trade, rent or otherwise share for marketing purposes your Personal
Information with third parties without your consent. We do share
Personal Information with vendors who are performing services for the
Company, such as the servers for our email communications who are
provided access to users email address for purposes of sending emails
from us. Those vendors use your Personal Information only at our
direction and in accordance with our Privacy Policy. In general, the
Personal Information you provide to us is used to help us communicate
with you. For example, we use Personal Information to contact users in
response to questions, solicit feedback from users, provide technical
support, and inform users about promotional offers. We may share
Personal Information with outside parties if we have a good-faith
belief that access, use, preservation or disclosure of the information
is reasonably necessary to meet any applicable legal process or
enforceable governmental request; to enforce applicable Terms of
Service, including investigation of potential violations; address
fraud, security or technical concerns; or to protect against harm to
the rights, property, or safety of our users or the public as required
or permitted by law.
</p>
<br />
<h1 className='text-1xl font-semibold text-slate-800'>
Non-Personal Information:
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
In general, we use Non-Personal Information to help us improve the
Service and customize the user experience. We also aggregate
Non-Personal Information in order to track trends and analyze use
patterns on the Site. This Privacy Policy does not limit in any way
our use or disclosure of Non-Personal Information and we reserve the
right to use and disclose such Non-Personal Information to our
partners, advertisers and other third parties at our discretion. In
the event we undergo a business transaction such as a merger,
acquisition by another company, or sale of all or a portion of our
assets, your Personal Information may be among the assets transferred.
You acknowledge and consent that such transfers may occur and are
permitted by this Privacy Policy, and that any acquirer of our assets
may continue to process your Personal Information as set forth in this
Privacy Policy. If our information practices change at any time in the
future, we will post the policy changes to the Site so that you may
opt out of the new information practices. We suggest that you check
the Site periodically if you are concerned about how your information
is used.
</p>
<br />
<h1 className='text-xl font-semibold text-slate-800'>
III. HOW WE PROTECT INFORMATION
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
We implement security measures designed to protect your information
from unauthorized access. Your account is protected by your account
password and we urge you to take steps to keep your personal
information safe by not disclosing your password and by logging out of
your account after each use. We further protect your information from
potential security breaches by implementing certain technological
security measures including encryption, firewalls and secure socket
layer technology. However, these measures do not guarantee that your
information will not be accessed, disclosed, altered or destroyed by
breach of such firewalls and secure server software. By using our
Service, you acknowledge that you understand and agree to assume these
risks.
</p>
<br />
<h1 className='text-xl font-semibold text-slate-800'>
IV. YOUR RIGHTS REGARDING THE USE OF YOUR PERSONAL INFORMATION
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
You have the right at any time to prevent us from contacting you for
marketing purposes. When we send a promotional communication to a
user, the user can opt out of further promotional communications by
following the unsubscribe instructions provided in each promotional
e-mail. You can also indicate that you do not wish to receive
marketing communications from us by contacting us at{' '}
<Link
href='mailto:ahmad.khan@outoforgedu.onmicrosoft.com'
className='text-blue-600/100 underline'
>
ahmad.khan@outoforgedu.onmicrosoft.com
</Link>
. Please note that notwithstanding the promotional preferences you
indicate by either unsubscribing or opting out via email, we may
continue to send you administrative emails including, for example,
periodic updates to our Privacy Policy.
</p>
<br />
<h1 className='text-xl font-semibold text-slate-800'>
V. LINKS TO OTHER WEBSITES
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
As part of the Service, we may provide links to or compatibility with
other websites or applications. However, we are not responsible for
the privacy practices employed by those websites or the information or
content they contain. This Privacy Policy applies solely to
information collected by us through the Site and the Service.
Therefore, this Privacy Policy does not apply to your use of a third
party website accessed by selecting a link on our Site or via our
Service. To the extent that you access or use the Service through or
on another website or application, then the privacy policy of that
other website or application will apply to your access or use of that
site or application. We encourage our users to read the privacy
statements of other websites before proceeding to use them.
</p>
<br />
<h1 className='text-xl font-semibold text-slate-800'>
VI. CHANGES TO OUR PRIVACY POLICY
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
The Company reserves the right to change this policy and our Terms of
Service at any time. We will notify you of significant changes to our
Privacy Policy by sending a notice to the primary email address
specified in your account or by placing a prominent notice on our
site. Significant changes will go into effect 30 days following such
notification. Non-material changes or clarifications will take effect
immediately. You should periodically check the Site and this privacy
page for updates.
</p>
<br />
<h1 className='text-xl font-semibold text-slate-800'>
VII. CONTACT US
</h1>
<br />
<p className='text-md font-medium text-slate-700'>
If you have any questions regarding this Privacy Policy or the
practices of this Site, please contact us by sending an email to{' '}
<Link
href='mailto:ahmad.khan@outoforgedu.onmicrosoft.com'
className='text-blue-600/100 underline'
>
ahmad.khan@outoforgedu.onmicrosoft.com
</Link>
. Last Updated: This Privacy Policy was last updated on 02/20/2024.
</p>
</div>
</div>
);
};
export default PrivacyPolicyPage;

31
content-collections.ts Normal file
View file

@ -0,0 +1,31 @@
import { defineCollection, defineConfig } from '@content-collections/core';
import { compileMDX } from '@content-collections/mdx';
const posts = defineCollection({
name: 'BlogPosts',
directory: 'app/(main)/blog/posts',
include: '*.mdx',
schema: (z) => ({
title: z
.string()
.min(3, { message: 'Title must be at least 3 characters' })
.max(30, { message: 'Title must be at most 30 characters' }),
summary: z
.string()
.min(10, { message: 'Summary must be at least 10 characters' })
.max(50, { message: 'Summary must be at most 50 characters' }),
coverImage: z.string(),
datePublished: z.string(),
}),
transform: async (document, context) => {
const mdx = await compileMDX(context, document);
return {
...document,
mdx,
};
},
});
export default defineConfig({
collections: [posts],
});

7
mdx-components.tsx Normal file
View file

@ -0,0 +1,7 @@
import type { MDXComponents } from 'mdx/types';
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
};
}

View file

@ -1,8 +1,12 @@
import type { NextConfig } from 'next';
import { withContentCollections } from '@content-collections/next';
import createMDX from '@next/mdx';
const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
mdxRs: true,
},
images: {
remotePatterns: [
@ -16,6 +20,9 @@ const nextConfig: NextConfig = {
},
],
},
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
};
export default nextConfig;
const withMDX = createMDX({});
export default withContentCollections(withMDX(nextConfig));

View file

@ -12,11 +12,14 @@
"format:fix": "prettier --write --ignore-path .prettierignore ."
},
"dependencies": {
"@clerk/nextjs": "^6.3.2",
"@clerk/nextjs": "^6.3.4",
"@hello-pangea/dnd": "^17.0.0",
"@liveblocks/client": "^2.11.1",
"@liveblocks/client": "^2.12.0",
"@liveblocks/node": "^2.11.1",
"@liveblocks/react": "^2.11.1",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/mdx": "^15.0.3",
"@prisma/client": "^5.22.0",
"@prisma/extension-accelerate": "^1.2.1",
"@radix-ui/react-accordion": "^1.2.1",
@ -28,22 +31,21 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.4",
"@radix-ui/react-visually-hidden": "^1.1.0",
"@tanstack/react-query": "^5.59.20",
"@tanstack/react-query": "^5.60.4",
"@types/mdx": "^2.0.13",
"@vercel/analytics": "^1.4.0",
"@vercel/functions": "^1.5.0",
"@vercel/speed-insights": "^1.1.0",
"babel-plugin-react-compiler": "0.0.0-experimental-7449567-20240905",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"dompurify": "^3.2.0",
"eslint-plugin-react-compiler": "0.0.0-experimental-7670337-20240918",
"lodash": "^4.17.21",
"lucide-react": "^0.456.0",
"next": "^15.0.3",
"react": "19.0.0-rc.0",
"react": "19.0.0-rc.1",
"react-day-picker": "^9.3.0",
"react-dom": "19.0.0-rc.0",
"react-dom": "19.0.0-rc.1",
"sharp": "^0.33.5",
"sonner": "^1.7.0",
"stripe": "^17.3.1",
@ -55,10 +57,14 @@
"zustand": "^5.0.1"
},
"devDependencies": {
"@content-collections/core": "^0.7.3",
"@content-collections/mdx": "^0.2.0",
"@content-collections/next": "^0.2.3",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.14.0",
"@microsoft/eslint-formatter-sarif": "^3.1.0",
"@next/eslint-plugin-next": "15.0.3",
"@tailwindcss/typography": "^0.5.15",
"@types/dompurify": "^3",
"@types/lodash": "^4.17.13",
"@types/node": "^22.9.0",
@ -67,9 +73,12 @@
"@typescript-eslint/eslint-plugin": "^8.14.0",
"@typescript-eslint/parser": "^8.14.0",
"autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "^19.0.0-beta-a7bf2bd-20241110",
"eslint": "^9.14.0",
"eslint-config-next": "15.0.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-compiler": "^19.0.0-beta-a7bf2bd-20241110",
"mintlify": "^4.0.273",
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",

View file

@ -3,10 +3,10 @@ import type { Config } from 'tailwindcss';
const config = {
darkMode: ['class'],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
'./pages/**/*.{ts,tsx,md,mdx}',
'./components/**/*.{ts,tsx,md,mdx}',
'./app/**/*.{ts,tsx,md,mdx}',
'./src/**/*.{ts,tsx,md,mdx}',
],
prefix: '',
theme: {
@ -74,7 +74,7 @@ const config = {
},
},
},
plugins: [require('tailwindcss-animate')],
plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
} satisfies Config;
export default config;

View file

@ -19,7 +19,8 @@
}
],
"paths": {
"@/*": ["./*"]
"@/*": ["./*"],
"content-collections": ["./.content-collections/generated"]
},
"forceConsistentCasingInFileNames": true
},

7124
yarn.lock

File diff suppressed because it is too large Load diff